summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--system/db/config.go77
-rw-r--r--system/db/content.go182
-rw-r--r--system/db/init.go94
-rw-r--r--system/db/user.go73
4 files changed, 426 insertions, 0 deletions
diff --git a/system/db/config.go b/system/db/config.go
new file mode 100644
index 0000000..e421ff5
--- /dev/null
+++ b/system/db/config.go
@@ -0,0 +1,77 @@
+package db
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/url"
+
+ "github.com/boltdb/bolt"
+ "github.com/gorilla/schema"
+ "github.com/nilslice/cms/system/admin/config"
+)
+
+// SetConfig sets key:value pairs in the db for configuration settings
+func SetConfig(data url.Values) error {
+ err := store.Update(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("_config"))
+
+ cfg := &config.Config{}
+ 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(cfg, data)
+ if err != nil {
+ return err
+ }
+
+ j, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+
+ err = b.Put([]byte("settings"), j)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Config gets the value of a key in the configuration from the db
+func Config(key string) ([]byte, error) {
+ kv := make(map[string]interface{})
+
+ cfg, err := ConfigAll()
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(cfg, &kv)
+ if err != nil {
+ return nil, err
+ }
+
+ return []byte(kv[key].(string)), nil
+}
+
+// ConfigAll gets the configuration from the db
+func ConfigAll() ([]byte, error) {
+ val := &bytes.Buffer{}
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("_config"))
+ val.Write(b.Get([]byte("settings")))
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return val.Bytes(), nil
+}
diff --git a/system/db/content.go b/system/db/content.go
new file mode 100644
index 0000000..cbe4f14
--- /dev/null
+++ b/system/db/content.go
@@ -0,0 +1,182 @@
+package db
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/boltdb/bolt"
+ "github.com/gorilla/schema"
+ "github.com/nilslice/cms/content"
+ "github.com/nilslice/cms/management/editor"
+ "github.com/nilslice/cms/management/manager"
+)
+
+// SetContent inserts or updates values in the database.
+// The `target` argument is a string made up of namespace:id (string:int)
+func SetContent(target string, data url.Values) (int, error) {
+ t := strings.Split(target, ":")
+ ns, id := t[0], t[1]
+
+ // check if content id == -1 (indicating new post).
+ // if so, run an insert which will assign the next auto incremented int.
+ // this is done because boltdb begins its bucket auto increment value at 0,
+ // which is the zero-value of an int in the Item struct field for ID.
+ // this is a problem when the original first post (with auto ID = 0) gets
+ // overwritten by any new post, originally having no ID, defauting to 0.
+ if id == "-1" {
+ 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 := postToJSON(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 := postToJSON(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 postToJSON(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
+ }
+
+ slug, err := manager.Slug(post.(editor.Editable))
+ if err != nil {
+ return nil, err
+ }
+ post.(editor.Editable).SetSlug(slug)
+
+ // marshall content struct to json for db storage
+ j, err := json.Marshal(post)
+ if err != nil {
+ return nil, err
+ }
+
+ return j, nil
+}
+
+// Content retrives one item from the database. Non-existent values will return an empty []byte
+// The `target` argument is a string made up of namespace:id (string:int)
+func Content(target string) ([]byte, error) {
+ t := strings.Split(target, ":")
+ ns, id := t[0], t[1]
+
+ 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
+}
+
+// ContentAll retrives all items from the database within the provided namespace
+func ContentAll(namespace string) [][]byte {
+ var posts [][]byte
+ store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte(namespace))
+
+ len := b.Stats().KeyN
+ posts = make([][]byte, 0, len)
+
+ b.ForEach(func(k, v []byte) error {
+ posts = append(posts, v)
+
+ return nil
+ })
+
+ return nil
+ })
+
+ return posts
+}
diff --git a/system/db/init.go b/system/db/init.go
new file mode 100644
index 0000000..0db2955
--- /dev/null
+++ b/system/db/init.go
@@ -0,0 +1,94 @@
+package db
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+
+ "github.com/boltdb/bolt"
+ "github.com/nilslice/cms/content"
+ "github.com/nilslice/cms/system/admin/config"
+)
+
+var store *bolt.DB
+
+// Init creates a db connection, initializes db with required info, sets secrets
+func Init() {
+ var err error
+ store, err = bolt.Open("store.db", 0666, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = store.Update(func(tx *bolt.Tx) error {
+ // initialize db with all content type buckets
+ for t := range content.Types {
+ _, err := tx.CreateBucketIfNotExists([]byte(t))
+ if err != nil {
+ return err
+ }
+ }
+
+ // init db with other buckets as needed
+ buckets := []string{"_config", "_users"}
+ for _, name := range buckets {
+ _, err := tx.CreateBucketIfNotExists([]byte(name))
+ if err != nil {
+ return err
+ }
+ }
+
+ // seed db with configs structure if not present
+ b := tx.Bucket([]byte("_config"))
+ if b.Get([]byte("settings")) == nil {
+ j, err := json.Marshal(&config.Config{})
+ if err != nil {
+ return err
+ }
+
+ err = b.Put([]byte("settings"), j)
+ if err != nil {
+ return err
+ }
+ }
+
+ // clientSecret, err := Config("client_secret")
+ // if err != nil {
+ // return err
+ // }
+
+ // jwt.Secret(clientSecret)
+
+ return nil
+ })
+ if err != nil {
+ log.Fatal("Coudn't initialize db with buckets.", err)
+ }
+
+}
+
+// SystemInitComplete checks if there is at least 1 admin user in the db which
+// would indicate that the system has been configured to the minimum required.
+func SystemInitComplete() bool {
+ complete := false
+
+ err := store.View(func(tx *bolt.Tx) error {
+ users := tx.Bucket([]byte("_users"))
+ err := users.ForEach(func(k, v []byte) error {
+ complete = true
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ complete = false
+ log.Fatal(err)
+ }
+
+ fmt.Println("System Init Complete:", complete)
+ return complete
+}
diff --git a/system/db/user.go b/system/db/user.go
new file mode 100644
index 0000000..e205a47
--- /dev/null
+++ b/system/db/user.go
@@ -0,0 +1,73 @@
+package db
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+
+ "github.com/boltdb/bolt"
+ "github.com/nilslice/cms/system/admin/user"
+)
+
+// ErrUserExists is used for the db to report to admin user of existing user
+var ErrUserExists = errors.New("Error. User exists.")
+
+// SetUser sets key:value pairs in the db for user settings
+func SetUser(usr *user.User) (int, error) {
+ err := store.Update(func(tx *bolt.Tx) error {
+ email := []byte(usr.Email)
+ users := tx.Bucket([]byte("_users"))
+
+ // check if user is found by email, fail if nil
+ exists := users.Get(email)
+ if exists != nil {
+ return ErrUserExists
+ }
+
+ // get NextSequence int64 and set it as the User.ID
+ id, err := users.NextSequence()
+ if err != nil {
+ return err
+ }
+ usr.ID = int(id)
+
+ // marshal User to json and put into bucket
+ j, err := json.Marshal(usr)
+ if err != nil {
+ return err
+ }
+
+ err = users.Put([]byte(usr.Email), j)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return 0, err
+ }
+
+ return usr.ID, nil
+}
+
+// User gets the user by email from the db
+func User(email string) ([]byte, error) {
+ val := &bytes.Buffer{}
+ err := store.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket([]byte("_users"))
+ usr := b.Get([]byte(email))
+
+ _, err := val.Write(usr)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return val.Bytes(), nil
+}