summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/item.go (renamed from system/db/item.go)5
-rw-r--r--content/post.go14
-rw-r--r--content/types.go18
-rw-r--r--management/manager/manager.go10
-rw-r--r--server.go78
-rw-r--r--system/db/query.go156
-rw-r--r--system/server.go85
7 files changed, 266 insertions, 100 deletions
diff --git a/system/db/item.go b/content/item.go
index 23e8553..8b834e4 100644
--- a/system/db/item.go
+++ b/content/item.go
@@ -1,8 +1,7 @@
-// Package db ...
-package db
+package content
// Item should only be embedded into content type structs.
// Helper for DB-related actions
type Item struct {
- ID int `json:"_id"`
+ ID int `json:"id"`
}
diff --git a/content/post.go b/content/post.go
index 1dfb8fb..7bb36f6 100644
--- a/content/post.go
+++ b/content/post.go
@@ -18,7 +18,7 @@ type Post struct {
}
func init() {
- Types["Post"] = &Post{}
+ Types["Post"] = func() interface{} { return new(Post) }
}
// ContentID partially implements editor.Editable
@@ -28,30 +28,30 @@ func (p *Post) ContentID() int { return p.ID }
func (p *Post) Editor() *editor.Editor { return &p.editor }
// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable
-func (p Post) MarshalEditor() ([]byte, error) {
- view, err := editor.New(&p,
+func (p *Post) MarshalEditor() ([]byte, error) {
+ view, err := editor.New(p,
editor.Field{
- View: editor.Input("Title", &p, map[string]string{
+ View: editor.Input("Title", p, map[string]string{
"label": "Post Title",
"type": "text",
"placeholder": "Enter your Post Title here",
}),
},
editor.Field{
- View: editor.Textarea("Content", &p, map[string]string{
+ View: editor.Textarea("Content", p, map[string]string{
"label": "Content",
"placeholder": "Add the content of your post here",
}),
},
editor.Field{
- View: editor.Input("Author", &p, map[string]string{
+ View: editor.Input("Author", p, map[string]string{
"label": "Author",
"type": "text",
"placeholder": "Enter the author name here",
}),
},
editor.Field{
- View: editor.Input("Timestamp", &p, map[string]string{
+ View: editor.Input("Timestamp", p, map[string]string{
"label": "Publish Date",
"type": "date",
}),
diff --git a/content/types.go b/content/types.go
index 778f742..ede2b58 100644
--- a/content/types.go
+++ b/content/types.go
@@ -1,7 +1,21 @@
package content
-import "github.com/nilslice/cms/management/editor"
+const (
+ // ErrTypeNotRegistered means content type isn't registered (not found in Types map)
+ ErrTypeNotRegistered = `Error:
+There is no type registered for %[1]s
+
+Add this to the file which defines %[1]s{} in the 'content' package:
+--------------------------------------------------------------------------+
+
+func init() {
+ Types["%[1]s"] = func() interface{} { return new(%[1]s) }
+}
+
+--------------------------------------------------------------------------+
+`
+)
// Types is a map used to reference a type name to its actual Editable type
// mainly for lookups in /admin route based utilities
-var Types = make(map[string]editor.Editable)
+var Types = make(map[string]func() interface{})
diff --git a/management/manager/manager.go b/management/manager/manager.go
index 83ed63a..7bdebbf 100644
--- a/management/manager/manager.go
+++ b/management/manager/manager.go
@@ -4,15 +4,17 @@ import (
"bytes"
"fmt"
"html/template"
- "reflect"
"github.com/nilslice/cms/management/editor"
)
var html = `
+<a href="/admin/edit?type={{.Kind}}" class="button">New {{.Kind}}</a>
<div class="manager">
- <form method="post" action="/admin/edit?type={{.Kind}}&contentId={{.ID}}">
+ <form method="post" action="/admin/edit">
{{.Editor}}
+ <input type="hidden" name="id" value="{{.ID}}"/>
+ <input type="hidden" name="type" value="{{.Kind}}"/>
<input type="submit" value="Save"/>
</form>
</div>
@@ -25,7 +27,7 @@ type form struct {
}
// Manage ...
-func Manage(e editor.Editable) ([]byte, error) {
+func Manage(e editor.Editable, typeName string) ([]byte, error) {
v, err := e.MarshalEditor()
if err != nil {
return nil, fmt.Errorf("Couldn't marshal editor for content %T. %s", e, err.Error())
@@ -33,7 +35,7 @@ func Manage(e editor.Editable) ([]byte, error) {
f := form{
ID: e.ContentID(),
- Kind: reflect.TypeOf(e).Name(),
+ Kind: typeName,
Editor: template.HTML(v),
}
diff --git a/server.go b/server.go
deleted file mode 100644
index d7605f6..0000000
--- a/server.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/http"
-
- "github.com/nilslice/cms/content"
- "github.com/nilslice/cms/management/manager"
-)
-
-const (
- // ErrTypeNotRegistered means content type isn't registered (not found in content.Types map)
- ErrTypeNotRegistered = `Error:
-There is no type registered for %[1]s
-
-Add this to the file which defines %[1]s{} in the 'content' package:
---------------------------------+
-
-func init() {
- Types["%[1]s"] = %[1]s{}
-}
-
---------------------------------+
-`
-)
-
-func main() {
- // p := content.Post{
- // Title: []byte("Profound introduction"),
- // Content: []byte("<h3>H</h3>ello. My name is <em>Steve</em>."),
- // Author: []byte("Steve Manuel"),
- // Timestamp: []byte("2016-09-16"),
- // }
- // p.ID = 1
-
- http.HandleFunc("/admin/edit", func(res http.ResponseWriter, req *http.Request) {
- switch req.Method {
- case http.MethodGet:
- err := req.ParseForm()
- if err != nil {
- res.WriteHeader(http.StatusBadRequest)
- return
- }
-
- t := req.FormValue("type")
- contentType, ok := content.Types[t]
- if !ok {
- fmt.Fprintf(res, ErrTypeNotRegistered, t)
- return
- }
- view, err := manager.Manage(contentType)
- if err != nil {
- res.WriteHeader(http.StatusInternalServerError)
- return
- }
- res.Header().Set("Content-Type", "text/html")
- res.Write(view)
-
- case http.MethodPost:
- err := req.ParseForm()
- if err != nil {
- res.WriteHeader(http.StatusBadRequest)
- return
- }
-
- id := req.FormValue("contentId")
- if id == "0" {
- res.Write([]byte("This would create a new post"))
- return
- }
-
- res.Write([]byte("Updated post " + id))
- }
- })
-
- http.ListenAndServe(":8080", nil)
-
-}
diff --git a/system/db/query.go b/system/db/query.go
index c9cf37c..06e9c62 100644
--- a/system/db/query.go
+++ b/system/db/query.go
@@ -1,17 +1,161 @@
package db
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/boltdb/bolt"
+ "github.com/gorilla/schema"
+ "github.com/nilslice/cms/content"
+)
+
+var store *bolt.DB
+
+func init() {
+ var err error
+ store, err = bolt.Open("store.db", 0666, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
// Set inserts or updates values in the database.
-// The `key` argument is a string made up of namespace:id (string:int)
-func Set(key string) error {
+// The `target` argument is a string made up of namespace:id (string:int)
+func Set(target string, data url.Values) (int, error) {
+ t := strings.Split(target, ":")
+ ns, id := t[0], t[1]
- return nil
+ // check if content has an id, and if not get new one from target bucket
+ if len(id) == 0 {
+ return insert(ns, data)
+ }
+
+ return update(ns, id, data)
+}
+
+func update(ns, id string, data url.Values) (int, error) {
+ cid, err := strconv.Atoi(id)
+ if err != nil {
+ return 0, err
+ }
+
+ err = store.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucketIfNotExists([]byte(ns))
+ if err != nil {
+ return err
+ }
+
+ j, err := toJSON(ns, data)
+ if err != nil {
+ return err
+ }
+
+ err = b.Put([]byte(fmt.Sprintf("%d", cid)), j)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return 0, nil
+ }
+
+ return cid, nil
+}
+
+func insert(ns string, data url.Values) (int, error) {
+ var effectedID int
+ err := store.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucketIfNotExists([]byte(ns))
+ if err != nil {
+ return err
+ }
+
+ // get the next available ID and convert to string
+ // also set effectedID to int of ID
+ id, err := b.NextSequence()
+ if err != nil {
+ return err
+ }
+ cid := strconv.FormatUint(id, 10)
+ effectedID, err = strconv.Atoi(cid)
+ if err != nil {
+ return err
+ }
+ data.Add("id", cid)
+
+ j, err := toJSON(ns, data)
+ if err != nil {
+ return err
+ }
+
+ err = b.Put([]byte(cid), j)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return 0, err
+ }
+
+ return effectedID, nil
+}
+
+func toJSON(ns string, data url.Values) ([]byte, error) {
+ // find the content type and decode values into it
+ t, ok := content.Types[ns]
+ if !ok {
+ return nil, fmt.Errorf(content.ErrTypeNotRegistered, ns)
+ }
+ post := t()
+
+ dec := schema.NewDecoder()
+ dec.SetAliasTag("json") // allows simpler struct tagging when creating a content type
+ dec.IgnoreUnknownKeys(true) // will skip over form values submitted, but not in struct
+ err := dec.Decode(post, data)
+ if err != nil {
+ return nil, err
+ }
+
+ // marshall content struct to json for db storage
+ j, err := json.Marshal(post)
+ if err != nil {
+ return nil, err
+ }
+
+ return j, nil
}
// Get retrives one item from the database. Non-existent values will return an empty []byte
-// The `key` argument is a string made up of namespace:id (string:int)
-func Get(key string) []byte {
+// The `target` argument is a string made up of namespace:id (string:int)
+func Get(target string) ([]byte, error) {
+ t := strings.Split(target, ":")
+ ns, id := t[0], t[1]
- return nil
+ val := &bytes.Buffer{}
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte(ns))
+ _, err := val.Write(b.Get([]byte(id)))
+ if err != nil {
+ fmt.Println(err)
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return val.Bytes(), nil
}
// GetAll retrives all items from the database within the provided namespace
diff --git a/system/server.go b/system/server.go
new file mode 100644
index 0000000..4a83706
--- /dev/null
+++ b/system/server.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/nilslice/cms/content"
+ "github.com/nilslice/cms/management/editor"
+ "github.com/nilslice/cms/management/manager"
+ "github.com/nilslice/cms/system/db"
+)
+
+func main() {
+ http.HandleFunc("/admin/edit", func(res http.ResponseWriter, req *http.Request) {
+ switch req.Method {
+ case http.MethodGet:
+ q := req.URL.Query()
+ i := q.Get("id")
+ t := q.Get("type")
+ contentType, ok := content.Types[t]
+ if !ok {
+ fmt.Fprintf(res, content.ErrTypeNotRegistered, t)
+ return
+ }
+ post := contentType()
+
+ if i != "" {
+ fmt.Println("Need to show post id:", i, "(", t, ")")
+
+ data, err := db.Get(t + ":" + i)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ err = json.Unmarshal(data, post)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ }
+
+ view, err := manager.Manage(post.(editor.Editable), t)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ res.Header().Set("Content-Type", "text/html")
+ res.Write(view)
+
+ case http.MethodPost:
+ err := req.ParseForm()
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ cid := req.FormValue("id")
+ t := req.FormValue("type")
+ fmt.Println("query data: t=", t, "id=", cid)
+
+ id, err := db.Set(t+":"+cid, req.PostForm)
+ if err != nil {
+ fmt.Println(err)
+ res.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ fmt.Println(t, "post created:", id)
+ scheme := req.URL.Scheme
+ host := req.URL.Host
+ path := req.URL.Path
+ desURL := scheme + host + path + "?type=" + t + "&id=" + fmt.Sprintf("%d", id)
+ http.Redirect(res, req, desURL, http.StatusFound)
+ }
+ })
+
+ http.ListenAndServe(":8080", nil)
+
+}