diff options
-rw-r--r-- | system/api/handlers.go | 54 | ||||
-rw-r--r-- | system/api/server.go | 4 | ||||
-rw-r--r-- | system/db/addon.go | 22 | ||||
-rw-r--r-- | system/db/config.go | 78 | ||||
-rw-r--r-- | system/db/content.go | 46 | ||||
-rw-r--r-- | system/db/init.go | 3 | ||||
-rw-r--r-- | system/db/user.go | 20 |
7 files changed, 162 insertions, 65 deletions
diff --git a/system/api/handlers.go b/system/api/handlers.go index 2473c24..f073e16 100644 --- a/system/api/handlers.go +++ b/system/api/handlers.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "compress/gzip" "encoding/json" "log" "net/http" @@ -13,6 +14,7 @@ import ( "github.com/ponzu-cms/ponzu/system/item" ) +// deprecating from API, but going to provide code here in case someone wants it func typesHandler(res http.ResponseWriter, req *http.Request) { var types = []string{} for t, fn := range item.Types { @@ -27,7 +29,7 @@ func typesHandler(res http.ResponseWriter, req *http.Request) { return } - sendData(res, j, http.StatusOK) + sendData(res, req, j, http.StatusOK) } func contentsHandler(res http.ResponseWriter, req *http.Request) { @@ -91,7 +93,7 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { return } - sendData(res, j, http.StatusOK) + sendData(res, req, j, http.StatusOK) } func contentHandler(res http.ResponseWriter, req *http.Request) { @@ -134,7 +136,7 @@ func contentHandler(res http.ResponseWriter, req *http.Request) { return } - sendData(res, j, http.StatusOK) + sendData(res, req, j, http.StatusOK) } func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) { @@ -171,7 +173,7 @@ func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) { return } - sendData(res, j, http.StatusOK) + sendData(res, req, j, http.StatusOK) } func hide(it interface{}, res http.ResponseWriter, req *http.Request) bool { @@ -233,8 +235,8 @@ func toJSON(data []string) ([]byte, error) { // sendData should be used any time you want to communicate // data back to a foreign client -func sendData(res http.ResponseWriter, data []byte, code int) { - res, cors := responseWithCORS(res) +func sendData(res http.ResponseWriter, req *http.Request, data []byte, code int) { + res, cors := responseWithCORS(res, req) if !cors { return } @@ -256,21 +258,34 @@ func sendPreflight(res http.ResponseWriter) { return } -func responseWithCORS(res http.ResponseWriter) (http.ResponseWriter, bool) { +func responseWithCORS(res http.ResponseWriter, req *http.Request) (http.ResponseWriter, bool) { if db.ConfigCache("cors_disabled").(bool) == true { + // check origin matches config domain and Allow + domain := db.ConfigCache("domain").(string) + + // currently, this will check for exact match. will need feedback to + // determine if subdomains should be allowed or allow multiple domains + // in config + if req.Origin == domain { + // apply limited CORS headers and return + res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type") + res.Header().Set("Access-Control-Allow-Origin", domain) + return res, true + } + // disallow request res.WriteHeader(http.StatusForbidden) return res, false } - // apply CORS headers and return + // apply full CORS headers and return res.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type") res.Header().Set("Access-Control-Allow-Origin", "*") return res, true } -// CORS wraps a HandleFunc to respond to OPTIONS requests properly +// CORS wraps a HandlerFunc 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 { @@ -282,7 +297,7 @@ func CORS(next http.HandlerFunc) http.HandlerFunc { })) } -// Record wraps a HandleFunc to record API requests for analytical purposes +// Record wraps a HandlerFunc to record API requests for analytical purposes func Record(next http.HandlerFunc) http.HandlerFunc { return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { go analytics.Record(req) @@ -290,3 +305,22 @@ func Record(next http.HandlerFunc) http.HandlerFunc { next.ServeHTTP(res, req) }) } + +// Gzip wraps a HandlerFunc to compress responses when possible +func Gzip(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + // check if req header content-encoding supports gzip + if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { + // gzip response data + gzres, err := gzip.NewWriter(res) + if err != nil { + log.Println("Error creating gzip writer in Gzip middleware.") + next.ServeHTTP(res, req) + } + + next.ServeHTTP(gzres, req) + } + + next.ServeHTTP(res, req) + }) +} diff --git a/system/api/server.go b/system/api/server.go index 4b8b22e..0bdc48a 100644 --- a/system/api/server.go +++ b/system/api/server.go @@ -4,11 +4,9 @@ import "net/http" // Run adds Handlers to default http listener for API func Run() { - http.HandleFunc("/api/types", Record(CORS(typesHandler))) - http.HandleFunc("/api/contents", Record(CORS(contentsHandler))) http.HandleFunc("/api/content", Record(CORS(contentHandler))) - http.HandleFunc("/api/content/external", Record(CORS(externalContentHandler))) + http.HandleFunc("/api/content/external", Record(externalContentHandler)) } diff --git a/system/db/addon.go b/system/db/addon.go index f4621fa..a145293 100644 --- a/system/db/addon.go +++ b/system/db/addon.go @@ -24,6 +24,9 @@ func Addon(key string) ([]byte, error) { err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__addons")) + if b == nil { + return bolt.ErrBucketNotFound + } val := b.Get([]byte(key)) @@ -56,12 +59,16 @@ func SetAddon(data url.Values, kind interface{}) error { v, err := json.Marshal(kind) + k := data.Get("addon_reverse_dns") + if k == "" { + name := data.Get("addon_name") + return fmt.Errorf(`Addon "%s" has no identifier to use as key.`, name) + } + err = store.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__addons")) - k := data.Get("addon_reverse_dns") - if k == "" { - name := data.Get("addon_name") - return fmt.Errorf(`Addon "%s" has no identifier to use as key.`, name) + if b == nil { + return bolt.ErrBucketNotFound } err := b.Put([]byte(k), v) @@ -84,6 +91,10 @@ func AddonAll() [][]byte { err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__addons")) + if b == nil { + return bolt.ErrBucketNotFound + } + err := b.ForEach(func(k, v []byte) error { all = append(all, v) @@ -107,6 +118,9 @@ func AddonAll() [][]byte { func DeleteAddon(key string) error { err := store.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__addons")) + if b == nil { + bolt.ErrBucketNotFound + } if err := b.Delete([]byte(key)); err != nil { return err diff --git a/system/db/config.go b/system/db/config.go index 48da4b0..5a93353 100644 --- a/system/db/config.go +++ b/system/db/config.go @@ -22,49 +22,53 @@ func init() { // SetConfig sets key:value pairs in the db for configuration settings func SetConfig(data url.Values) error { var j []byte - err := store.Update(func(tx *bolt.Tx) error { - b := tx.Bucket([]byte("__config")) - // check for any multi-value fields (ex. checkbox fields) - // and correctly format for db storage. Essentially, we need - // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} - var discardKeys []string - for k, v := range data { - if strings.Contains(k, ".") { - key := strings.Split(k, ".")[0] - - if data.Get(key) == "" { - data.Set(key, v[0]) - } else { - data.Add(key, v[0]) - } - - discardKeys = append(discardKeys, k) + // check for any multi-value fields (ex. checkbox fields) + // and correctly format for db storage. Essentially, we need + // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} + var discardKeys []string + for k, v := range data { + if strings.Contains(k, ".") { + key := strings.Split(k, ".")[0] + + if data.Get(key) == "" { + data.Set(key, v[0]) + } else { + data.Add(key, v[0]) } - } - for _, discardKey := range discardKeys { - data.Del(discardKey) + discardKeys = append(discardKeys, k) } + } - 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 - } + for _, discardKey := range discardKeys { + data.Del(discardKey) + } - // check for "invalidate" value to reset the Etag - if len(cfg.CacheInvalidate) > 0 && cfg.CacheInvalidate[0] == "invalidate" { - cfg.Etag = NewEtag() - cfg.CacheInvalidate = []string{} - } + 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 + // check for "invalidate" value to reset the Etag + if len(cfg.CacheInvalidate) > 0 && cfg.CacheInvalidate[0] == "invalidate" { + cfg.Etag = NewEtag() + cfg.CacheInvalidate = []string{} + } + + j, err = json.Marshal(cfg) + if err != nil { + return err + } + + err = store.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("__config")) + if b == nil { + return bolt.ErrBucketNotFound } err = b.Put([]byte("settings"), j) @@ -117,7 +121,7 @@ func ConfigAll() ([]byte, error) { err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__config")) if b == nil { - return fmt.Errorf("Error finding bucket: %s", "__config") + return bolt.ErrBucketNotFound } _, err := val.Write(b.Get([]byte("settings"))) if err != nil { diff --git a/system/db/content.go b/system/db/content.go index 010e5cb..d9096ae 100644 --- a/system/db/content.go +++ b/system/db/content.go @@ -49,17 +49,17 @@ func update(ns, id string, data url.Values) (int, error) { return 0, err } + j, err := postToJSON(ns, data) + if err != nil { + return 0, err + } + err = store.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(ns + specifier)) 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 @@ -134,6 +134,10 @@ func insert(ns string, data url.Values) (int, error) { // store the slug,type:id in contentIndex if public content if specifier == "" { ci := tx.Bucket([]byte("__contentIndex")) + if ci == nil { + return bolt.ErrBucketNotFound + } + k := []byte(data.Get("slug")) v := []byte(fmt.Sprintf("%s:%d", ns, effectedID)) err := ci.Put(k, v) @@ -168,7 +172,12 @@ func DeleteContent(target string, data url.Values) error { ns, id := t[0], t[1] err := store.Update(func(tx *bolt.Tx) error { - err := tx.Bucket([]byte(ns)).Delete([]byte(id)) + b := tx.Bucket([]byte(ns)) + if b == nil { + return bolt.ErrBucketNotFound + } + + err := b.Delete([]byte(id)) if err != nil { return err } @@ -176,7 +185,12 @@ func DeleteContent(target string, data url.Values) error { // if content has a slug, also delete it from __contentIndex slug := data.Get("slug") if slug != "" { - err := tx.Bucket([]byte("__contentIndex")).Delete([]byte(slug)) + ci := tx.Bucket([]byte("__contentIndex")) + if ci == nil { + return bolt.ErrBucketNotFound + } + + err := ci.Delete([]byte(slug)) if err != nil { return err } @@ -212,6 +226,10 @@ func Content(target string) ([]byte, error) { val := &bytes.Buffer{} err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(ns)) + if b == nil { + return bolt.ErrBucketNotFound + } + _, err := val.Write(b.Get([]byte(id))) if err != nil { log.Println(err) @@ -235,6 +253,9 @@ func ContentBySlug(slug string) (string, []byte, error) { var t, id string err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__contentIndex")) + if b == nil { + return bolt.ErrBucketNotFound + } idx := b.Get([]byte(slug)) if idx != nil { @@ -248,6 +269,9 @@ func ContentBySlug(slug string) (string, []byte, error) { } c := tx.Bucket([]byte(t)) + if c == nil { + return bolt.ErrBucketNotFound + } _, err := val.Write(c.Get([]byte(id))) if err != nil { return err @@ -267,9 +291,8 @@ func ContentAll(namespace string) [][]byte { var posts [][]byte store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(namespace)) - if b == nil { - return nil + return bolt.ErrBucketNotFound } numKeys := b.Stats().KeyN @@ -313,7 +336,7 @@ func Query(namespace string, opts QueryOptions) (int, [][]byte) { store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(namespace)) if b == nil { - return nil + return bolt.ErrBucketNotFound } c := b.Cursor() @@ -535,6 +558,9 @@ 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")) + if b == nil { + return bolt.ErrBucketNotFound + } original := slug exists := true i := 0 diff --git a/system/db/init.go b/system/db/init.go index 0e640b1..9125d3b 100644 --- a/system/db/init.go +++ b/system/db/init.go @@ -92,6 +92,9 @@ func SystemInitComplete() bool { err := store.View(func(tx *bolt.Tx) error { users := tx.Bucket([]byte("__users")) + if users == nil { + return bolt.ErrBucketNotFound + } err := users.ForEach(func(k, v []byte) error { complete = true diff --git a/system/db/user.go b/system/db/user.go index 02fda95..164ae7b 100644 --- a/system/db/user.go +++ b/system/db/user.go @@ -26,6 +26,9 @@ func SetUser(usr *user.User) (int, error) { err := store.Update(func(tx *bolt.Tx) error { email := []byte(usr.Email) users := tx.Bucket([]byte("__users")) + if users == nil { + return bolt.ErrBucketNotFound + } // check if user is found by email, fail if nil exists := users.Get(email) @@ -69,6 +72,9 @@ func UpdateUser(usr, updatedUsr *user.User) error { err := store.Update(func(tx *bolt.Tx) error { users := tx.Bucket([]byte("__users")) + if users == nil { + return bolt.ErrBucketNotFound + } // check if user is found by email, fail if nil exists := users.Get([]byte(usr.Email)) @@ -110,6 +116,10 @@ func UpdateUser(usr, updatedUsr *user.User) error { func DeleteUser(email string) error { err := store.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__users")) + if b == nil { + return bolt.ErrBucketNotFound + } + err := b.Delete([]byte(email)) if err != nil { return err @@ -129,6 +139,10 @@ func User(email string) ([]byte, error) { val := &bytes.Buffer{} err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__users")) + if b == nil { + return bolt.ErrBucketNotFound + } + usr := b.Get([]byte(email)) _, err := val.Write(usr) @@ -154,6 +168,10 @@ func UserAll() ([][]byte, error) { var users [][]byte err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__users")) + if b == nil { + return bolt.ErrBucketNotFound + } + err := b.ForEach(func(k, v []byte) error { users = append(users, v) return nil @@ -230,7 +248,7 @@ func RecoveryKey(email string) (string, error) { err := store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__recoveryKeys")) if b == nil { - return errors.New("No database found for checking keys.") + return bolt.ErrBucketNotFound } _, err := key.Write(b.Get([]byte(email))) |