summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2017-04-29 22:43:44 -0500
committerSteve Manuel <nilslice@gmail.com>2017-04-29 22:43:44 -0500
commit3c8c848606b996e2c7a06331401e622f888b84c5 (patch)
treea8c5b6ce9492d33d18bf2a66bc0ec13c9fffc538
parenta48ed8dc7f7ddb3ddbc8ea54ad0b0d2feb7c87f0 (diff)
adding search, edit/new, and list view for uploads
-rw-r--r--system/admin/handlers.go185
-rw-r--r--system/admin/server.go2
-rw-r--r--system/api/handlers.go2
-rw-r--r--system/db/upload.go109
-rw-r--r--system/item/upload.go40
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)"
+}