summaryrefslogtreecommitdiff
path: root/system
diff options
context:
space:
mode:
Diffstat (limited to 'system')
-rw-r--r--system/addon/api.go4
-rw-r--r--system/admin/admin.go2
-rw-r--r--system/admin/handlers.go6
-rw-r--r--system/api/handlers.go88
-rw-r--r--system/api/push.go37
-rw-r--r--system/db/content.go10
-rw-r--r--system/item/item.go13
-rw-r--r--system/item/types.go5
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