diff options
-rw-r--r-- | system/db/config.go | 77 | ||||
-rw-r--r-- | system/db/content.go | 182 | ||||
-rw-r--r-- | system/db/init.go | 94 | ||||
-rw-r--r-- | system/db/user.go | 73 |
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 +} |