summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/item.go6
-rw-r--r--management/manager/manager.go10
-rw-r--r--system/admin/handlers.go9
-rw-r--r--system/api/handlers.go26
-rw-r--r--system/db/content.go114
-rw-r--r--system/db/init.go2
6 files changed, 158 insertions, 9 deletions
diff --git a/content/item.go b/content/item.go
index e6c8243..eb79aa0 100644
--- a/content/item.go
+++ b/content/item.go
@@ -13,6 +13,7 @@ import (
// and it will override the slug created by Item's SetSlug with your struct's
type Sluggable interface {
SetSlug(string)
+ ItemSlug() string
}
// Identifiable enables a struct to have its ID set/get. Typically this is done
@@ -67,6 +68,11 @@ func (i *Item) SetSlug(slug string) {
i.Slug = slug
}
+// ItemSlug sets the item's slug for its URL
+func (i *Item) ItemSlug() string {
+ return i.Slug
+}
+
// ItemID gets the Item's ID field
// partially implements the Identifiable interface
func (i Item) ItemID() int {
diff --git a/management/manager/manager.go b/management/manager/manager.go
index 6083f73..989eb98 100644
--- a/management/manager/manager.go
+++ b/management/manager/manager.go
@@ -16,6 +16,7 @@ const managerHTML = `
<input type="hidden" name="uuid" value="{{.UUID}}"/>
<input type="hidden" name="id" value="{{.ID}}"/>
<input type="hidden" name="type" value="{{.Kind}}"/>
+ <input type="hidden" name="slug" value="{{.Slug}}"/>
{{ .Editor }}
</form>
<script>
@@ -109,6 +110,7 @@ type manager struct {
ID int
UUID uuid.UUID
Kind string
+ Slug string
Editor template.HTML
}
@@ -121,13 +123,19 @@ func Manage(e editor.Editable, typeName string) ([]byte, error) {
i, ok := e.(content.Identifiable)
if !ok {
- return nil, fmt.Errorf("Content type %s does not implement content.Sortable.", typeName)
+ return nil, fmt.Errorf("Content type %s does not implement content.Identifiable.", typeName)
+ }
+
+ s, ok := e.(content.Sluggable)
+ if !ok {
+ return nil, fmt.Errorf("Content type %s does not implement content.Sluggable.", typeName)
}
m := manager{
ID: i.ItemID(),
UUID: i.UniqueID(),
Kind: typeName,
+ Slug: s.ItemSlug(), // TODO: just added this and its implementation -- need to rebuild & test
Editor: template.HTML(v),
}
diff --git a/system/admin/handlers.go b/system/admin/handlers.go
index c91db79..7fb1692 100644
--- a/system/admin/handlers.go
+++ b/system/admin/handlers.go
@@ -1150,6 +1150,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
dec.SetAliasTag("json")
err = dec.Decode(post, req.Form)
if err != nil {
+ log.Println("Error decoding post form for content approval:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1162,6 +1163,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
err = hook.BeforeApprove(req)
if err != nil {
+ log.Println("Error running BeforeApprove hook in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1175,6 +1177,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
// call its Approve method
err = m.Approve(req)
if err != nil {
+ log.Println("Error running Approve method in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1187,6 +1190,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
err = hook.AfterApprove(req)
if err != nil {
+ log.Println("Error running AfterApprove hook in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1199,6 +1203,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
err = hook.BeforeSave(req)
if err != nil {
+ log.Println("Error running BeforeSave hook in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1212,6 +1217,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
// Store the content in the bucket t
id, err := db.SetContent(t+":-1", req.Form)
if err != nil {
+ log.Println("Error storing content in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1224,6 +1230,7 @@ func approveContentHandler(res http.ResponseWriter, req *http.Request) {
err = hook.AfterSave(req)
if err != nil {
+ log.Println("Error running AfterSave hook in approveContentHandler for:", t, err)
res.WriteHeader(http.StatusInternalServerError)
errView, err := Error500()
if err != nil {
@@ -1567,7 +1574,7 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) {
return
}
- err = db.DeleteContent(t + ":" + id)
+ err = db.DeleteContent(t+":"+id, req.Form)
if err != nil {
log.Println(err)
res.WriteHeader(http.StatusInternalServerError)
diff --git a/system/api/handlers.go b/system/api/handlers.go
index c238ca9..7a2073d 100644
--- a/system/api/handlers.go
+++ b/system/api/handlers.go
@@ -91,6 +91,12 @@ func contentHandler(res http.ResponseWriter, req *http.Request) {
q := req.URL.Query()
id := q.Get("id")
t := q.Get("type")
+ slug := q.Get("slug")
+
+ if slug != "" {
+ contentHandlerBySlug(res, req)
+ return
+ }
if _, ok := content.Types[t]; !ok {
res.WriteHeader(http.StatusNotFound)
@@ -117,6 +123,26 @@ func contentHandler(res http.ResponseWriter, req *http.Request) {
sendData(res, j, http.StatusOK)
}
+func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) {
+ slug := req.URL.Query().Get("slug")
+
+ // lookup type:id by slug key in __contentIndex
+ post, err := db.ContentBySlug(slug)
+ if err != nil {
+ log.Println("Error finding content by slug:", slug, err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ j, err := fmtJSON(json.RawMessage(post))
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ sendData(res, j, http.StatusOK)
+}
+
func fmtJSON(data ...json.RawMessage) ([]byte, error) {
var msg = []json.RawMessage{}
for _, d := range data {
diff --git a/system/db/content.go b/system/db/content.go
index 19c31d7..87b3e69 100644
--- a/system/db/content.go
+++ b/system/db/content.go
@@ -118,6 +118,11 @@ func insert(ns string, data url.Values) (int, error) {
uid := uuid.NewV4()
data.Set("uuid", uid.String())
+ // if type has a specifier, add it to data for downstream processing
+ if specifier != "" {
+ data.Set("__specifier", specifier)
+ }
+
j, err := postToJSON(ns, data)
if err != nil {
return err
@@ -128,6 +133,17 @@ func insert(ns string, data url.Values) (int, error) {
return err
}
+ // store the slug,type:id in contentIndex if public content
+ if specifier == "" {
+ ci := tx.Bucket([]byte("__contentIndex"))
+ k := []byte(data.Get("slug"))
+ v := []byte(fmt.Sprintf("%s:%d", ns, effectedID))
+ err := ci.Put(k, v)
+ if err != nil {
+ return err
+ }
+ }
+
return nil
})
if err != nil {
@@ -149,12 +165,25 @@ func insert(ns string, data url.Values) (int, error) {
// DeleteContent removes an item from the database. Deleting a non-existent item
// will return a nil error.
-func DeleteContent(target string) error {
+func DeleteContent(target string, data url.Values) error {
t := strings.Split(target, ":")
ns, id := t[0], t[1]
err := store.Update(func(tx *bolt.Tx) error {
- tx.Bucket([]byte(ns)).Delete([]byte(id))
+ err := tx.Bucket([]byte(ns)).Delete([]byte(id))
+ if err != nil {
+ return err
+ }
+
+ // if content has a slug, also delete it from __contentIndex
+ slug := data.Get("slug")
+ if slug != "" {
+ err := tx.Bucket([]byte("__contentIndex")).Delete([]byte(slug))
+ if err != nil {
+ return err
+ }
+ }
+
return nil
})
if err != nil {
@@ -200,6 +229,41 @@ func Content(target string) ([]byte, error) {
return val.Bytes(), nil
}
+// 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) {
+ val := &bytes.Buffer{}
+ err := store.View(func(tx *bolt.Tx) error {
+ var t, id string
+ b := tx.Bucket([]byte("__contentIndex"))
+ idx := b.Get([]byte(slug))
+
+ if idx != nil {
+ tid := strings.Split(string(idx), ":")
+
+ if len(tid) < 2 {
+ return fmt.Errorf("Bad data in content index for slug: %s", slug)
+ }
+
+ t, id = tid[0], tid[1]
+ }
+
+ c := tx.Bucket([]byte(t))
+ _, err := val.Write(c.Get([]byte(id)))
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return val.Bytes(), nil
+}
+
// ContentAll retrives all items from the database within the provided namespace
func ContentAll(namespace string) [][]byte {
var posts [][]byte
@@ -435,11 +499,22 @@ func postToJSON(ns string, data url.Values) ([]byte, error) {
return nil, err
}
- slug, err := manager.Slug(post.(content.Identifiable))
- if err != nil {
- return nil, err
+ // if the content has no slug, and has no specifier, create a slug, check it
+ // for duplicates, and add it to our values
+ if data.Get("slug") == "" && data.Get("__specifier") == "" {
+ slug, err := manager.Slug(post.(content.Identifiable))
+ if err != nil {
+ return nil, err
+ }
+
+ slug, err = checkSlugForDuplicate(slug)
+ if err != nil {
+ return nil, err
+ }
+
+ post.(content.Sluggable).SetSlug(slug)
+ data.Set("slug", slug)
}
- post.(content.Sluggable).SetSlug(slug)
// marshall content struct to json for db storage
j, err := json.Marshal(post)
@@ -449,3 +524,30 @@ func postToJSON(ns string, data url.Values) ([]byte, error) {
return j, nil
}
+
+func checkSlugForDuplicate(slug string) (string, error) {
+ // check for existing slug in __contentIndex
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("__contentIndex"))
+ original := slug
+ exists := true
+ i := 0
+ for exists {
+ s := b.Get([]byte(slug))
+ if s == nil {
+ exists = false
+ return nil
+ }
+
+ i++
+ slug = fmt.Sprintf("%s-%d", original, i)
+ }
+
+ return nil
+ })
+ if err != nil {
+ return "", err
+ }
+
+ return slug, nil
+}
diff --git a/system/db/init.go b/system/db/init.go
index 967eed1..df993e6 100644
--- a/system/db/init.go
+++ b/system/db/init.go
@@ -45,7 +45,7 @@ func Init() {
}
// init db with other buckets as needed
- buckets := []string{"__config", "__users"}
+ buckets := []string{"__config", "__users", "__contentIndex"}
for _, name := range buckets {
_, err := tx.CreateBucketIfNotExists([]byte(name))
if err != nil {