diff options
Diffstat (limited to 'system')
-rw-r--r-- | system/addon/api.go | 4 | ||||
-rw-r--r-- | system/admin/admin.go | 2 | ||||
-rw-r--r-- | system/admin/handlers.go | 6 | ||||
-rw-r--r-- | system/api/handlers.go | 88 | ||||
-rw-r--r-- | system/api/push.go | 37 | ||||
-rw-r--r-- | system/db/content.go | 10 | ||||
-rw-r--r-- | system/item/item.go | 13 | ||||
-rw-r--r-- | system/item/types.go | 5 |
8 files changed, 127 insertions, 38 deletions
diff --git a/system/addon/api.go b/system/addon/api.go index 39f1b4d..7167202 100644 --- a/system/addon/api.go +++ b/system/addon/api.go @@ -25,7 +25,7 @@ func ContentAll(namespace string) []byte { j, err := Get(URL) if err != nil { - log.Println("Error in ContentAll for reference HTTP request:", endpoint) + log.Println("Error in ContentAll for reference HTTP request:", URL) return nil } @@ -42,7 +42,7 @@ func Query(namespace string, opts QueryOptions) []byte { j, err := Get(URL) if err != nil { - log.Println("Error in Query for reference HTTP request:", endpoint) + log.Println("Error in Query for reference HTTP request:", URL) return nil } diff --git a/system/admin/admin.go b/system/admin/admin.go index 76f36f6..e3ae2d6 100644 --- a/system/admin/admin.go +++ b/system/admin/admin.go @@ -487,7 +487,7 @@ var analyticsHTML = ` <div class="analytics"> <div class="card"> <div class="card-content"> - <p class="right">Data range: {{ .from }} - {{ .to }} (GMT)</p> + <p class="right">Data range: {{ .from }} - {{ .to }} (UTC)</p> <div class="card-title">API Requests</div> <canvas id="analytics-chart"></canvas> <script> diff --git a/system/admin/handlers.go b/system/admin/handlers.go index ff30040..1ff7156 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -909,7 +909,7 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { for i := range posts { err := json.Unmarshal(posts[i], &p) if err != nil { - log.Println("Error unmarshal json into", t, err, posts[i]) + log.Println("Error unmarshal json into", t, err, string(posts[i])) post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` b.Write([]byte(post)) @@ -934,7 +934,7 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { for i := len(posts) - 1; i >= 0; i-- { err := json.Unmarshal(posts[i], &p) if err != nil { - log.Println("Error unmarshal json into", t, err, posts[i]) + log.Println("Error unmarshal json into", t, err, string(posts[i])) post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` b.Write([]byte(post)) @@ -950,7 +950,7 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { for i := range posts { err := json.Unmarshal(posts[i], &p) if err != nil { - log.Println("Error unmarshal json into", t, err, posts[i]) + log.Println("Error unmarshal json into", t, err, string(posts[i])) post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` b.Write([]byte(post)) diff --git a/system/api/handlers.go b/system/api/handlers.go index 7b59dbd..8b4a387 100644 --- a/system/api/handlers.go +++ b/system/api/handlers.go @@ -15,8 +15,10 @@ import ( func typesHandler(res http.ResponseWriter, req *http.Request) { var types = []string{} - for t := range item.Types { - types = append(types, string(t)) + for t, fn := range item.Types { + if !hide(fn(), res, req) { + types = append(types, t) + } } j, err := toJSON(types) @@ -36,11 +38,16 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { return } - if _, ok := item.Types[t]; !ok { + it, ok := item.Types[t] + if !ok { res.WriteHeader(http.StatusNotFound) return } + if hide(it(), res, req) { + return + } + count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all) if err != nil { if q.Get("count") == "" { @@ -98,13 +105,18 @@ func contentHandler(res http.ResponseWriter, req *http.Request) { return } - if _, ok := item.Types[t]; !ok { + if t == "" || id == "" { + res.WriteHeader(http.StatusBadRequest) + return + } + + pt, ok := item.Types[t] + if !ok { res.WriteHeader(http.StatusNotFound) return } - if t == "" || id == "" { - res.WriteHeader(http.StatusBadRequest) + if hide(pt(), res, req) { return } @@ -114,6 +126,8 @@ func contentHandler(res http.ResponseWriter, req *http.Request) { return } + defer push(res, req, pt, post) + j, err := fmtJSON(json.RawMessage(post)) if err != nil { res.WriteHeader(http.StatusInternalServerError) @@ -126,14 +140,31 @@ func contentHandler(res http.ResponseWriter, req *http.Request) { func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) { slug := req.URL.Query().Get("slug") + if slug == "" { + res.WriteHeader(http.StatusBadRequest) + return + } + // lookup type:id by slug key in __contentIndex - post, err := db.ContentBySlug(slug) + t, post, err := db.ContentBySlug(slug) if err != nil { log.Println("Error finding content by slug:", slug, err) res.WriteHeader(http.StatusInternalServerError) return } + it, ok := item.Types[t] + if !ok { + res.WriteHeader(http.StatusBadRequest) + return + } + + if hide(it(), res, req) { + return + } + + defer push(res, req, it, post) + j, err := fmtJSON(json.RawMessage(post)) if err != nil { res.WriteHeader(http.StatusInternalServerError) @@ -143,6 +174,26 @@ func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) { sendData(res, j, http.StatusOK) } +func hide(it interface{}, res http.ResponseWriter, req *http.Request) bool { + // check if should be hidden + if h, ok := it.(item.Hideable); ok { + err := h.Hide(req) + if err != nil && err.Error() == item.AllowHiddenItem { + return false + } + + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + return true + } + + res.WriteHeader(http.StatusNotFound) + return true + } + + return false +} + func fmtJSON(data ...json.RawMessage) ([]byte, error) { var msg = []json.RawMessage{} for _, d := range data { @@ -193,36 +244,19 @@ func sendData(res http.ResponseWriter, data []byte, code int) { } } -// SendPreflight is used to respond to a cross-origin "OPTIONS" request -func SendPreflight(res http.ResponseWriter) { +// sendPreflight is used to respond to a cross-origin "OPTIONS" request +func sendPreflight(res http.ResponseWriter) { res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type") res.Header().Set("Access-Control-Allow-Origin", "*") res.WriteHeader(200) return } -// SendJSON returns a Response to a client as JSON -func SendJSON(res http.ResponseWriter, j map[string]interface{}) { - var data []byte - var err error - - data, err = json.Marshal(j) - if err != nil { - log.Println(err) - data, _ = json.Marshal(map[string]interface{}{ - "status": "fail", - "message": err.Error(), - }) - } - - sendData(res, data, 200) -} - // CORS wraps a HandleFunc to respond to OPTIONS requests properly func CORS(next http.HandlerFunc) http.HandlerFunc { return db.CacheControl(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if req.Method == http.MethodOptions { - SendPreflight(res) + sendPreflight(res) return } diff --git a/system/api/push.go b/system/api/push.go new file mode 100644 index 0000000..5db0a53 --- /dev/null +++ b/system/api/push.go @@ -0,0 +1,37 @@ +package api + +import ( + "log" + "net/http" + + "github.com/ponzu-cms/ponzu/system/item" + + "github.com/tidwall/gjson" +) + +func push(res http.ResponseWriter, req *http.Request, pt func() interface{}, data []byte) { + // Push(target string, opts *PushOptions) error + if pusher, ok := res.(http.Pusher); ok { + if p, ok := pt().(item.Pushable); ok { + // get fields to pull values from data + fields := p.Push() + + // parse values from data to push + values := gjson.GetManyBytes(data, fields...) + + // push all values from Pushable items' fields + for i := range values { + val := values[i] + val.ForEach(func(k, v gjson.Result) bool { + err := pusher.Push(req.URL.Path+v.String(), nil) + if err != nil { + log.Println("Error during Push of value:", v.String()) + } + + return true + }) + } + } + } + +} diff --git a/system/db/content.go b/system/db/content.go index 8bc76a6..dc4477f 100644 --- a/system/db/content.go +++ b/system/db/content.go @@ -229,11 +229,11 @@ func Content(target string) ([]byte, error) { // ContentBySlug does a lookup in the content index to find the type and id of // the requested content. Subsequently, issues the lookup in the type bucket and -// returns the data at that ID or nil if nothing exists. -func ContentBySlug(slug string) ([]byte, error) { +// returns the the type and data at that ID or nil if nothing exists. +func ContentBySlug(slug string) (string, []byte, error) { val := &bytes.Buffer{} + var t, id string err := store.View(func(tx *bolt.Tx) error { - var t, id string b := tx.Bucket([]byte("__contentIndex")) idx := b.Get([]byte(slug)) @@ -256,10 +256,10 @@ func ContentBySlug(slug string) ([]byte, error) { return nil }) if err != nil { - return nil, err + return t, nil, err } - return val.Bytes(), nil + return t, val.Bytes(), nil } // ContentAll retrives all items from the database within the provided namespace diff --git a/system/item/item.go b/system/item/item.go index a813669..761b2cf 100644 --- a/system/item/item.go +++ b/system/item/item.go @@ -55,6 +55,19 @@ type Hookable interface { AfterReject(req *http.Request) error } +// Hideable lets a user keep items hidden +type Hideable interface { + Hide(*http.Request) error +} + +// Pushable lets a user define which values of certain struct fields are +// 'pushed' down to a client via HTTP/2 Server Push. All items in the slice +// should be the json tag names of the struct fields to which they coorespond +type Pushable interface { + // the values contained by fields returned by Push must strictly be URL paths + Push() []string +} + // Item should only be embedded into content type structs. type Item struct { UUID uuid.UUID `json:"uuid"` diff --git a/system/item/types.go b/system/item/types.go index 33e9ced..b4b361b 100644 --- a/system/item/types.go +++ b/system/item/types.go @@ -14,6 +14,11 @@ Add this to the file which defines %[1]s{} in the 'content' package: ` + + // AllowHiddenItem should be used as an error to tell a caller of Hideable#Hide + // that this type is hidden, but should be shown in a particular case, i.e. + // if requested by a valid admin or user + AllowHiddenItem = `Allow hidden item` ) // Types is a map used to reference a type name to its actual Editable type |