diff options
author | Steve Manuel <nilslice@gmail.com> | 2017-04-29 22:43:44 -0500 |
---|---|---|
committer | Steve Manuel <nilslice@gmail.com> | 2017-04-29 22:43:44 -0500 |
commit | 3c8c848606b996e2c7a06331401e622f888b84c5 (patch) | |
tree | a8c5b6ce9492d33d18bf2a66bc0ec13c9fffc538 | |
parent | a48ed8dc7f7ddb3ddbc8ea54ad0b0d2feb7c87f0 (diff) |
adding search, edit/new, and list view for uploads
-rw-r--r-- | system/admin/handlers.go | 185 | ||||
-rw-r--r-- | system/admin/server.go | 2 | ||||
-rw-r--r-- | system/api/handlers.go | 2 | ||||
-rw-r--r-- | system/db/upload.go | 109 | ||||
-rw-r--r-- | system/item/upload.go | 40 |
5 files changed, 222 insertions, 116 deletions
diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 7780151..ac18020 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -912,7 +912,7 @@ func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { var path = window.location.pathname; var s = sort.val(); - window.location.replace(path + '&order=' + s); + window.location.replace(path + '?order=' + s); }); var order = getParam('order'); @@ -924,7 +924,7 @@ func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { </script> </div> </div> - <form class="col s4" action="/admin/contents/search" method="get"> + <form class="col s4" action="/admin/uploads/search" method="get"> <div class="input-field post-search inline"> <label class="active">Search:</label> <i class="right material-icons search-icon">search</i> @@ -1073,7 +1073,7 @@ func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { </script> ` - btn := `<div class="col s3"><a href="/admin/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` + btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` html = html + b.String() + script + btn adminView, err := Admin([]byte(html)) @@ -1539,9 +1539,14 @@ func adminPostListItem(e editor.Editable, typeName, status string) []byte { status = "__" + status } + link := `<a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "__") + `&id=` + cid + `">` + i.String() + `</a>` + if strings.HasPrefix(typeName, "__") { + link = `<a href="/admin/edit/upload?id=` + cid + `">` + i.String() + `</a>` + } + post := ` <li class="col s12"> - <a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "__") + `&id=` + cid + `">` + i.String() + `</a> + ` + link + ` <span class="post-detail">Updated: ` + updatedTime + `</span> <span class="publish-date right">` + publishTime + `</span> @@ -2149,7 +2154,6 @@ func editUploadHandler(res http.ResponseWriter, req *http.Request) { return } - cid := req.FormValue("id") t := req.FormValue("type") pt := "__uploads" ts := req.FormValue("timestamp") @@ -2165,6 +2169,27 @@ func editUploadHandler(res http.ResponseWriter, req *http.Request) { req.PostForm.Set("updated", ts) } + post := interface{}(&item.FileUpload{}) + hook, ok := post.(item.Hookable) + if !ok { + log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") + res.WriteHeader(http.StatusBadRequest) + errView, err := Error400() + if err != nil { + return + } + + res.Write(errView) + return + } + + err = hook.BeforeSave(res, req) + if err != nil { + log.Println("Error running BeforeSave method in editHandler for:", t, err) + return + } + + // StoreFiles has the SetUpload call (which is equivalent of SetContent in other handlers) urlPaths, err := upload.StoreFiles(req) if err != nil { log.Println(err) @@ -2229,43 +2254,6 @@ func editUploadHandler(res http.ResponseWriter, req *http.Request) { } } - post := interface{}(&item.FileUpload{}) - hook, ok := post.(item.Hookable) - if !ok { - log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.") - res.WriteHeader(http.StatusBadRequest) - errView, err := Error400() - if err != nil { - return - } - - res.Write(errView) - return - } - - err = hook.BeforeSave(res, req) - if err != nil { - log.Println("Error running BeforeSave method in editHandler for:", t, err) - return - } - - id, err := db.SetUpload(t+":"+cid, req.PostForm) - if err != nil { - log.Println(err) - res.WriteHeader(http.StatusInternalServerError) - errView, err := Error500() - if err != nil { - return - } - - res.Write(errView) - return - } - - // set the target in the context so user can get saved value from db in hook - ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id)) - req = req.WithContext(ctx) - err = hook.AfterSave(res, req) if err != nil { log.Println("Error running AfterSave method in editHandler for:", t, err) @@ -2274,15 +2262,9 @@ func editUploadHandler(res http.ResponseWriter, req *http.Request) { scheme := req.URL.Scheme host := req.URL.Host - path := req.URL.Path - sid := fmt.Sprintf("%d", id) - redir := scheme + host + path + "?type=" + pt + "&id=" + sid - - if req.URL.Query().Get("status") == "pending" { - redir += "&status=pending" - } - + redir := scheme + host + "/admin/uploads" http.Redirect(res, req, redir, http.StatusFound) + case http.MethodPut: urlPaths, err := upload.StoreFiles(req) if err != nil { @@ -2427,6 +2409,109 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { res.Write(adminView) } +func uploadSearchHandler(res http.ResponseWriter, req *http.Request) { + q := req.URL.Query() + t := "__uploads" + search := q.Get("q") + status := q.Get("status") + + if t == "" || search == "" { + http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) + return + } + + posts := db.UploadAll() + b := &bytes.Buffer{} + p := interface{}(&item.FileUpload{}).(editor.Editable) + + html := `<div class="col s9 card"> + <div class="card-content"> + <div class="row"> + <div class="card-title col s7">Uploads Results</div> + <form class="col s4" action="/admin/uploads/search" method="get"> + <div class="input-field post-search inline"> + <label class="active">Search:</label> + <i class="right material-icons search-icon">search</i> + <input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/> + <input type="hidden" name="type" value="` + t + `" /> + </div> + </form> + </div> + <ul class="posts row">` + + for i := range posts { + // skip posts that don't have any matching search criteria + match := strings.ToLower(search) + all := strings.ToLower(string(posts[i])) + if !strings.Contains(all, match) { + continue + } + + err := json.Unmarshal(posts[i], &p) + if err != nil { + log.Println("Error unmarshal search result json into", t, err, posts[i]) + + post := `<li class="col s12">Error decoding data. Possible file corruption.</li>` + _, err = b.Write([]byte(post)) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + continue + } + + post := adminPostListItem(p, t, status) + _, err = b.Write([]byte(post)) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + } + + _, err := b.WriteString(`</ul></div></div>`) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + + btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>` + html = html + b.String() + btn + + adminView, err := Admin([]byte(html)) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) +} + func addonsHandler(res http.ResponseWriter, req *http.Request) { switch req.Method { case http.MethodGet: diff --git a/system/admin/server.go b/system/admin/server.go index 3ee44f9..b029be4 100644 --- a/system/admin/server.go +++ b/system/admin/server.go @@ -33,7 +33,7 @@ func Run() { http.HandleFunc("/admin/configure/users/delete", user.Auth(configUsersDeleteHandler)) http.HandleFunc("/admin/uploads", user.Auth(uploadContentsHandler)) - // http.HandleFunc("/admin/uploads/search", user.Auth(uploadSearchHandler)) + http.HandleFunc("/admin/uploads/search", user.Auth(uploadSearchHandler)) http.HandleFunc("/admin/contents", user.Auth(contentsHandler)) http.HandleFunc("/admin/contents/search", user.Auth(searchHandler)) diff --git a/system/api/handlers.go b/system/api/handlers.go index b8b90df..0a9c177 100644 --- a/system/api/handlers.go +++ b/system/api/handlers.go @@ -210,7 +210,7 @@ func uploadsHandler(res http.ResponseWriter, req *http.Request) { upload, err := db.UploadBySlug(slug) if err != nil { log.Println("Error finding upload by slug:", slug, err) - res.WriteHeader(http.StatusInternalServerError) + res.WriteHeader(http.StatusNotFound) return } diff --git a/system/db/upload.go b/system/db/upload.go index b05f505..956b7f9 100644 --- a/system/db/upload.go +++ b/system/db/upload.go @@ -2,16 +2,17 @@ package db import ( "bytes" + "encoding/binary" "encoding/json" "fmt" + "log" "net/url" + "strconv" "strings" "time" "github.com/ponzu-cms/ponzu/system/item" - "strconv" - "github.com/boltdb/bolt" "github.com/gorilla/schema" uuid "github.com/satori/go.uuid" @@ -25,7 +26,8 @@ func SetUpload(target string, data url.Values) (int, error) { } pid := parts[1] - if data.Get("uuid") == "" { + if data.Get("uuid") == "" || + data.Get("uuid") == (uuid.UUID{}).String() { // set new UUID for upload data.Set("uuid", uuid.NewV4().String()) } @@ -48,8 +50,9 @@ func SetUpload(target string, data url.Values) (int, error) { data.Set("updated", ts) // store in database - var id int64 - err := store.Update(func(tx *bolt.Tx) error { + var id uint64 + var err error + err = store.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte("__uploads")) if err != nil { return err @@ -57,16 +60,18 @@ func SetUpload(target string, data url.Values) (int, error) { if pid == "-1" { // get sequential ID for item - id, err := b.NextSequence() + id, err = b.NextSequence() if err != nil { return err } data.Set("id", fmt.Sprintf("%d", id)) } else { - id, err = strconv.ParseInt(pid, 10, 64) + uid, err := strconv.ParseInt(pid, 10, 64) if err != nil { return err } + id = uint64(uid) + data.Set("id", fmt.Sprintf("%d", id)) } file := &item.FileUpload{} @@ -84,7 +89,11 @@ func SetUpload(target string, data url.Values) (int, error) { return err } - err = b.Put([]byte(data.Get("id")), j) + uploadKey, err := key(data.Get("id")) + if err != nil { + return err + } + err = b.Put(uploadKey, j) if err != nil { return err } @@ -96,7 +105,8 @@ func SetUpload(target string, data url.Values) (int, error) { } k := []byte(data.Get("slug")) - v := []byte(fmt.Sprintf("%s:%d", "__uploads", id)) + v := []byte(fmt.Sprintf("__uploads:%d", id)) + err = b.Put(k, v) if err != nil { return err @@ -119,9 +129,12 @@ func Upload(target string) ([]byte, error) { return nil, fmt.Errorf("invalid target for upload: %s", target) } - id := []byte(parts[1]) + id, err := key(parts[1]) + if err != nil { + return nil, err + } - err := store.View(func(tx *bolt.Tx) error { + err = store.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("__uploads")) if b == nil { return bolt.ErrBucketNotFound @@ -145,11 +158,12 @@ func UploadBySlug(slug string) ([]byte, error) { return bolt.ErrBucketNotFound } - target := b.Get([]byte(slug)) - if target == nil { - return fmt.Errorf("no value for target in %s", "__contentIndex") + v := b.Get([]byte(slug)) + if v == nil { + return fmt.Errorf("no value for key '%s' in __contentIndex", slug) } - j, err := Upload(string(target)) + + j, err := Upload(string(v)) if err != nil { return err } @@ -162,33 +176,38 @@ func UploadBySlug(slug string) ([]byte, error) { return val.Bytes(), err } -// func replaceUpload(id string, data url.Values) error { -// // Unmarsal the existing values -// s := t() -// err := json.Unmarshal(existingContent, &s) -// if err != nil { -// log.Println("Error decoding json while updating", ns, ":", err) -// return j, err -// } - -// // Don't allow the Item fields to be updated from form values -// data.Del("id") -// data.Del("uuid") -// data.Del("slug") - -// 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(s, data) -// if err != nil { -// return j, err -// } - -// j, err = json.Marshal(s) -// if err != nil { -// return j, err -// } - -// return j, nil -// return nil -// } +// UploadAll returns a [][]byte containing all upload data from the system +func UploadAll() [][]byte { + var uploads [][]byte + err := store.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("__uploads")) + if b == nil { + return bolt.ErrBucketNotFound + } + + numKeys := b.Stats().KeyN + uploads = make([][]byte, 0, numKeys) + + return b.ForEach(func(k, v []byte) error { + uploads = append(uploads, v) + return nil + }) + }) + if err != nil { + log.Println("Error in UploadAll:", err) + return nil + } + + return uploads +} + +func key(sid string) ([]byte, error) { + id, err := strconv.Atoi(sid) + if err != nil { + return nil, err + } + + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(id)) + return b, err +} diff --git a/system/item/upload.go b/system/item/upload.go index 9253fcc..800f663 100644 --- a/system/item/upload.go +++ b/system/item/upload.go @@ -2,6 +2,7 @@ package item import ( "fmt" + "time" "github.com/ponzu-cms/ponzu/management/editor" ) @@ -30,15 +31,12 @@ func (f *FileUpload) MarshalEditor() ([]byte, error) { return []byte(` <div class="input-field col s12"> - <label class="active">` + f.Name + `</label> <!-- Add your custom editor field view here. --> - <h4>` + f.Name + `</h4> - - <img class="preview" src="` + f.Path + `"/> - <p>File information:</p> + <h5>` + f.Name + `</h5> <ul> - <li>Content-Length: ` + fmt.Sprintf("%s", FmtBytes(float64(f.ContentLength))) + `</li> - <li>Content-Type: ` + f.ContentType + `</li> + <li><span class="grey-text text-lighten-1">Content-Length:</span> ` + fmt.Sprintf("%s", FmtBytes(float64(f.ContentLength))) + `</li> + <li><span class="grey-text text-lighten-1">Content-Type:</span> ` + f.ContentType + `</li> + <li><span class="grey-text text-lighten-1">Uploaded:</span> ` + FmtTime(f.Timestamp) + `</li> </ul> </div> `) @@ -55,17 +53,13 @@ func (f *FileUpload) MarshalEditor() ([]byte, error) { return nil, err } - open := []byte(` - <div class="card"> - <div class="card-content"> - <div class="card-title">File Uploads</div> - </div> - <form action="/admin/uploads" method="post"> - `) - close := []byte(`</form></div>`) script := []byte(` <script> $(function() { + // change form action to upload-specific endpoint + var form = $('form'); + form.attr('action', '/admin/edit/upload'); + // hide default fields & labels unnecessary for the config var fields = $('.default-fields'); fields.css('position', 'relative'); @@ -88,14 +82,17 @@ func (f *FileUpload) MarshalEditor() ([]byte, error) { fields.find('input[name=client_secret]').attr('name', ''); // hide save, show delete - fields.find('.save-post').hide(); - fields.find('.delete-post').show(); + if ($('h5').length > 0) { + fields.find('.save-post').hide(); + fields.find('.delete-post').show(); + } else { + fields.find('.save-post').show(); + fields.find('.delete-post').hide(); + } }); </script> `) - view = append(open, view...) - view = append(view, close...) view = append(view, script...) return view, nil @@ -136,3 +133,8 @@ func FmtBytes(size float64) string { } } + +// FmtTime shows a human readable time based on the timestamp +func FmtTime(t int64) string { + return time.Unix(t/1000, 0).Format("03:04 PM Jan 2, 2006") + " (UTC)" +} |