From 099d000119447708d7d0d0482758d352438fa7e5 Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Tue, 25 Apr 2017 13:23:37 -0700 Subject: adding support for file upload type and API handler to fetch file info --- system/admin/upload/upload.go | 25 +++++++- system/api/handlers.go | 41 ++++++++++++ system/api/server.go | 4 +- system/auth.go | 37 ----------- system/db/init.go | 5 +- system/db/upload.go | 142 ++++++++++++++++++++++++++++++++++++++++++ system/item/file.go | 96 ++++++++++++++++++++++++++++ system/system.go | 37 +++++++++++ 8 files changed, 346 insertions(+), 41 deletions(-) delete mode 100644 system/auth.go create mode 100644 system/db/upload.go create mode 100644 system/item/file.go create mode 100644 system/system.go (limited to 'system') diff --git a/system/admin/upload/upload.go b/system/admin/upload/upload.go index dbcdc17..35fb8f9 100644 --- a/system/admin/upload/upload.go +++ b/system/admin/upload/upload.go @@ -5,12 +5,16 @@ package upload import ( "fmt" "io" + "log" + "mime/multipart" "net/http" + "net/url" "os" "path/filepath" "strconv" "time" + "github.com/ponzu-cms/ponzu/system/db" "github.com/ponzu-cms/ponzu/system/item" ) @@ -86,16 +90,33 @@ func StoreFiles(req *http.Request) (map[string]string, error) { } // copy file from src to dst on disk - if _, err = io.Copy(dst, src); err != nil { + var size int64 + if size, err = io.Copy(dst, src); err != nil { err := fmt.Errorf("Failed to copy uploaded file to destination: %s", err) return nil, err } // add name:urlPath to req.PostForm to be inserted into db urlPath := fmt.Sprintf("/%s/%s/%d/%02d/%s", urlPathPrefix, uploadDirName, tm.Year(), tm.Month(), filename) - urlPaths[name] = urlPath + + // add upload information to db + go storeFileInfo(size, filename, urlPath, fds) } return urlPaths, nil } + +func storeFileInfo(size int64, filename, urlPath string, fds []*multipart.FileHeader) { + data := url.Values{ + "name": []string{filename}, + "path": []string{urlPath}, + "content_type": []string{fds[0].Header.Get("Content-Type")}, + "content_length": []string{fmt.Sprintf("%d", size)}, + } + + err := db.SetUpload(data) + if err != nil { + log.Println("Error saving file upload record to database:", err) + } +} diff --git a/system/api/handlers.go b/system/api/handlers.go index 83bbe43..b8b90df 100644 --- a/system/api/handlers.go +++ b/system/api/handlers.go @@ -194,3 +194,44 @@ func contentHandlerBySlug(res http.ResponseWriter, req *http.Request) { sendData(res, req, j) } + +func uploadsHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodGet { + res.WriteHeader(http.StatusMethodNotAllowed) + return + } + + slug := req.URL.Query().Get("slug") + if slug == "" { + res.WriteHeader(http.StatusBadRequest) + return + } + + upload, err := db.UploadBySlug(slug) + if err != nil { + log.Println("Error finding upload by slug:", slug, err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + it := func() interface{} { + return new(item.FileUpload) + } + + push(res, req, it, upload) + + j, err := fmtJSON(json.RawMessage(upload)) + if err != nil { + log.Println("Error fmtJSON on upload:", err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + j, err = omit(it(), j) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + + sendData(res, req, j) +} diff --git a/system/api/server.go b/system/api/server.go index a7bd056..b51936c 100644 --- a/system/api/server.go +++ b/system/api/server.go @@ -17,5 +17,7 @@ func Run() { http.HandleFunc("/api/content/delete", Record(CORS(deleteContentHandler))) - http.HandleFunc("/api/search", Record(CORS(searchContentHandler))) + http.HandleFunc("/api/search", Record(CORS(Gzip(searchContentHandler)))) + + http.HandleFunc("/api/uploads", Record(CORS(Gzip(uploadsHandler)))) } diff --git a/system/auth.go b/system/auth.go deleted file mode 100644 index 8b12ab5..0000000 --- a/system/auth.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package system contains a collection of packages that make up the internal -// Ponzu system, which handles addons, administration, the Admin server, the API -// server, analytics, databases, search, TLS, and various internal types. -package system - -import ( - "net/http" - - "github.com/ponzu-cms/ponzu/system/db" -) - -// BasicAuth adds HTTP Basic Auth check for requests that should implement it -func BasicAuth(next http.HandlerFunc) http.HandlerFunc { - return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { - u := db.ConfigCache("backup_basic_auth_user").(string) - p := db.ConfigCache("backup_basic_auth_password").(string) - - if u == "" || p == "" { - res.WriteHeader(http.StatusForbidden) - return - } - - user, password, ok := req.BasicAuth() - - if !ok { - res.WriteHeader(http.StatusForbidden) - return - } - - if u != user || p != password { - res.WriteHeader(http.StatusUnauthorized) - return - } - - next.ServeHTTP(res, req) - }) -} diff --git a/system/db/init.go b/system/db/init.go index eb5f7ee..a401a2a 100644 --- a/system/db/init.go +++ b/system/db/init.go @@ -51,7 +51,10 @@ func Init() { } // init db with other buckets as needed - buckets := []string{"__config", "__users", "__contentIndex", "__addons"} + buckets := []string{ + "__config", "__users", "__contentIndex", + "__addons", "__uploads", + } for _, name := range buckets { _, err := tx.CreateBucketIfNotExists([]byte(name)) if err != nil { diff --git a/system/db/upload.go b/system/db/upload.go new file mode 100644 index 0000000..3157f13 --- /dev/null +++ b/system/db/upload.go @@ -0,0 +1,142 @@ +package db + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + "strings" + "time" + + "github.com/ponzu-cms/ponzu/system/item" + + "github.com/boltdb/bolt" + "github.com/gorilla/schema" + uuid "github.com/satori/go.uuid" +) + +// SetUpload stores information about files uploaded to the system +func SetUpload(data url.Values) error { + // set new UUID for upload + data.Set("uuid", uuid.NewV4().String()) + + // create slug based on filename and timestamp/updated fields + slug := data.Get("name") + slug, err := checkSlugForDuplicate(slug) + if err != nil { + return err + } + data.Set("slug", slug) + + ts := fmt.Sprintf("%d", time.Now().Unix()*1000) + data.Set("timestamp", ts) + data.Set("updated", ts) + + // store in database + err = store.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("__uploads")) + if err != nil { + return err + } + + // get sequential ID for item + id, err := b.NextSequence() + if err != nil { + return err + } + data.Set("id", fmt.Sprintf("%d", id)) + + file := &item.FileUpload{} + 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(file, data) + if err != nil { + return err + } + + // marshal data to json for storage + j, err := json.Marshal(file) + if err != nil { + return err + } + + err = b.Put([]byte(data.Get("id")), j) + if err != nil { + return err + } + + return nil + }) + if err != nil { + return err + } + + // add slug to __contentIndex for lookup + return store.Update(func(tx *bolt.Tx) error { + b, err := tx.CreateBucketIfNotExists([]byte("__contentIndex")) + if err != nil { + return err + } + + k := []byte(data.Get("slug")) + v := []byte(fmt.Sprintf("%s:%s", "__uploads", data.Get("id"))) + err = b.Put(k, v) + if err != nil { + return err + } + + return nil + }) +} + +// Upload returns the value for an upload by its target (__uploads:{id}) +func Upload(target string) ([]byte, error) { + val := &bytes.Buffer{} + parts := strings.Split(target, ":") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid target for upload: %s", target) + } + + id := []byte(parts[1]) + + err := store.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("__uploads")) + if b == nil { + return bolt.ErrBucketNotFound + } + + j := b.Get(id) + _, err := val.Write(j) + return err + }) + + return val.Bytes(), err +} + +// UploadBySlug returns the value for an upload by its slug +func UploadBySlug(slug string) ([]byte, error) { + val := &bytes.Buffer{} + // get target from __contentIndex or return nil if not exists + err := store.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("__contentIndex")) + if b == nil { + return bolt.ErrBucketNotFound + } + + target := b.Get([]byte(slug)) + if target == nil { + return fmt.Errorf("no value for target in %s", "__contentIndex") + } + j, err := Upload(string(target)) + if err != nil { + return err + } + + _, err = val.Write(j) + + return err + }) + + return val.Bytes(), err +} diff --git a/system/item/file.go b/system/item/file.go new file mode 100644 index 0000000..3b33b04 --- /dev/null +++ b/system/item/file.go @@ -0,0 +1,96 @@ +package item + +import ( + "fmt" + + "github.com/ponzu-cms/ponzu/management/editor" +) + +// FileUpload represents the file uploaded to the system +type FileUpload struct { + Item + + Name string `json:"name"` + Path string `json:"path"` + ContentLength int64 `json:"content_length"` + ContentType string `json:"content_type"` +} + +// String partially implements item.Identifiable and overrides Item's String() +func (f *FileUpload) String() string { return f.Name } + +// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable +func (f *FileUpload) MarshalEditor() ([]byte, error) { + view, err := editor.Form(f, + editor.Field{ + View: []byte(` +
+ + +

` + f.Name + `

+ + +

File information:

+ +
+ `), + }, + ) + if err != nil { + return nil, err + } + + open := []byte(` +
+
+
File Uploads
+
+
+ `) + close := []byte(`
`) + script := []byte(` + + `) + + view = append(open, view...) + view = append(view, close...) + view = append(view, script...) + + return view, nil +} + +func (f *FileUpload) Push() []string { + return []string{ + "path", + } +} diff --git a/system/system.go b/system/system.go new file mode 100644 index 0000000..8b12ab5 --- /dev/null +++ b/system/system.go @@ -0,0 +1,37 @@ +// Package system contains a collection of packages that make up the internal +// Ponzu system, which handles addons, administration, the Admin server, the API +// server, analytics, databases, search, TLS, and various internal types. +package system + +import ( + "net/http" + + "github.com/ponzu-cms/ponzu/system/db" +) + +// BasicAuth adds HTTP Basic Auth check for requests that should implement it +func BasicAuth(next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + u := db.ConfigCache("backup_basic_auth_user").(string) + p := db.ConfigCache("backup_basic_auth_password").(string) + + if u == "" || p == "" { + res.WriteHeader(http.StatusForbidden) + return + } + + user, password, ok := req.BasicAuth() + + if !ok { + res.WriteHeader(http.StatusForbidden) + return + } + + if u != user || p != password { + res.WriteHeader(http.StatusUnauthorized) + return + } + + next.ServeHTTP(res, req) + }) +} -- cgit v1.2.3 From f869855ed484721e669bc2ae1acfa25471bae258 Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Thu, 27 Apr 2017 08:19:06 -0700 Subject: fixing typo and removing ui classes where not needed --- system/admin/admin.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'system') diff --git a/system/admin/admin.go b/system/admin/admin.go index 7761e71..09750f0 100644 --- a/system/admin/admin.go +++ b/system/admin/admin.go @@ -368,41 +368,41 @@ func UsersList(req *http.Request) ([]byte, error) {
Edit your account:
-
+
-
+
To approve changes, enter your password:
-
+
-
+
Add a new user:
-
+
-
+
-
+
-- cgit v1.2.3 From 910874aeb538863ac1b9768843b98eb9b013f47e Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Thu, 27 Apr 2017 14:43:06 -0700 Subject: move the insert to contentIndex to the same Update call --- system/db/upload.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'system') diff --git a/system/db/upload.go b/system/db/upload.go index 3157f13..cef4595 100644 --- a/system/db/upload.go +++ b/system/db/upload.go @@ -66,28 +66,24 @@ func SetUpload(data url.Values) error { return err } - return nil - }) - if err != nil { - return err - } - - // add slug to __contentIndex for lookup - return store.Update(func(tx *bolt.Tx) error { - b, err := tx.CreateBucketIfNotExists([]byte("__contentIndex")) + // add slug to __contentIndex for lookup + b, err = tx.CreateBucketIfNotExists([]byte("__contentIndex")) if err != nil { return err } k := []byte(data.Get("slug")) - v := []byte(fmt.Sprintf("%s:%s", "__uploads", data.Get("id"))) + v := []byte(fmt.Sprintf("%s:%d", "__uploads", id)) err = b.Put(k, v) if err != nil { return err } + // - return nil }) + + return err } // Upload returns the value for an upload by its target (__uploads:{id}) -- cgit v1.2.3 From 7fffb0b422f8306f709f38b029cc0a03e583184c Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Sat, 29 Apr 2017 11:21:14 -0700 Subject: adding view and links to admin/manager --- system/admin/admin.go | 1 + system/admin/handlers.go | 260 +++++++++++++++++++++++++++++++++++++++++++++++ system/admin/server.go | 2 + 3 files changed, 263 insertions(+) (limited to 'system') diff --git a/system/admin/admin.go b/system/admin/admin.go index 09750f0..9e57678 100644 --- a/system/admin/admin.go +++ b/system/admin/admin.go @@ -66,6 +66,7 @@ var mainAdminHTML = ` diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 4734ba0..0433b76 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -827,6 +827,266 @@ func recoveryKeyHandler(res http.ResponseWriter, req *http.Request) { } } +func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { + q := req.URL.Query() + + order := strings.ToLower(q.Get("order")) + if order != "asc" { + order = "desc" + } + + pt := interface{}(&item.FileUpload{}) + + p, ok := pt.(editor.Editable) + if !ok { + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all) + if err != nil { + if q.Get("count") == "" { + count = 10 + } else { + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + } + + offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default) + if err != nil { + if q.Get("offset") == "" { + offset = 0 + } else { + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + } + + opts := db.QueryOptions{ + Count: count, + Offset: offset, + Order: order, + } + + b := &bytes.Buffer{} + var total int + var posts [][]byte + + html := `
+
+
+
+
+
Uploaded Items
+
+ + +
+ +
+
+
+
+ + search + + +
+
+
` + + t := "__uploads" + status := "" + total, posts = db.Query(t, opts) + + for i := range posts { + err := json.Unmarshal(posts[i], &p) + if err != nil { + log.Println("Error unmarshal json into", t, err, string(posts[i])) + + post := `
  • Error decoding data. Possible file corruption.
  • ` + _, 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(post) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + } + + html += `
      ` + + _, err = b.Write([]byte(`
    `)) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + + statusDisabled := "disabled" + prevStatus := "" + nextStatus := "" + // total may be less than 10 (default count), so reset count to match total + if total < count { + count = total + } + // nothing previous to current list + if offset == 0 { + prevStatus = statusDisabled + } + // nothing after current list + if (offset+1)*count >= total { + nextStatus = statusDisabled + } + + // set up pagination values + urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s" + prevURL := fmt.Sprintf(urlFmt, count, offset-1, order) + nextURL := fmt.Sprintf(urlFmt, count, offset+1, order) + start := 1 + count*offset + end := start + count - 1 + + if total < end { + end = total + } + + pagination := fmt.Sprintf(` + + `, prevStatus, prevURL, start, end, total, nextStatus, nextURL) + + // show indicator that a collection of items will be listed implicitly, but + // that none are created yet + if total < 1 { + pagination = ` + + ` + } + + _, err = b.Write([]byte(pagination + `
    `)) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + + script := ` + + ` + + btn := `
    ` + html = html + b.String() + script + 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 contentsHandler(res http.ResponseWriter, req *http.Request) { q := req.URL.Query() t := q.Get("type") diff --git a/system/admin/server.go b/system/admin/server.go index df00c21..94aca78 100644 --- a/system/admin/server.go +++ b/system/admin/server.go @@ -32,6 +32,8 @@ func Run() { http.HandleFunc("/admin/configure/users/edit", user.Auth(configUsersEditHandler)) http.HandleFunc("/admin/configure/users/delete", user.Auth(configUsersDeleteHandler)) + http.HandleFunc("/admin/uploads", user.Auth(uploadContentsHandler)) + http.HandleFunc("/admin/contents", user.Auth(contentsHandler)) http.HandleFunc("/admin/contents/search", user.Auth(searchHandler)) -- cgit v1.2.3 From 7ecfc3ac37f3a00522dd486f4b7e7e7c1850de47 Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Sat, 29 Apr 2017 11:24:47 -0700 Subject: update nav url and icon --- system/admin/admin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'system') diff --git a/system/admin/admin.go b/system/admin/admin.go index 9e57678..ab3fda6 100644 --- a/system/admin/admin.go +++ b/system/admin/admin.go @@ -66,7 +66,7 @@ var mainAdminHTML = ` -- cgit v1.2.3 From a48ed8dc7f7ddb3ddbc8ea54ad0b0d2feb7c87f0 Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Sat, 29 Apr 2017 14:39:35 -0700 Subject: fmt bytes to readable and update setupload logic to allow edits --- system/admin/handlers.go | 245 ++++++++++++++++++++++++++++++++++++++++++ system/admin/server.go | 1 + system/admin/upload/upload.go | 2 +- system/db/upload.go | 92 ++++++++++++---- system/item/file.go | 96 ----------------- system/item/upload.go | 138 ++++++++++++++++++++++++ 6 files changed, 459 insertions(+), 115 deletions(-) delete mode 100644 system/item/file.go create mode 100644 system/item/upload.go (limited to 'system') diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 0433b76..7780151 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -2056,6 +2056,250 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, redir, http.StatusFound) } +func editUploadHandler(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + q := req.URL.Query() + i := q.Get("id") + t := "__uploads" + + post := &item.FileUpload{} + + if i != "" { + data, err := db.Upload(t + ":" + i) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + if len(data) < 1 || data == nil { + res.WriteHeader(http.StatusNotFound) + errView, err := Error404() + if err != nil { + return + } + + res.Write(errView) + return + } + + err = json.Unmarshal(data, post) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + } else { + it, ok := interface{}(post).(item.Identifiable) + if !ok { + log.Println("Content type", t, "doesn't implement item.Identifiable") + return + } + + it.SetItemID(-1) + } + + m, err := manager.Manage(interface{}(post).(editor.Editable), t) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + adminView, err := Admin(m) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) + + case http.MethodPost: + err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + cid := req.FormValue("id") + t := req.FormValue("type") + pt := "__uploads" + ts := req.FormValue("timestamp") + up := req.FormValue("updated") + + // create a timestamp if one was not set + if ts == "" { + ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond)) + req.PostForm.Set("timestamp", ts) + } + + if up == "" { + req.PostForm.Set("updated", ts) + } + + urlPaths, err := upload.StoreFiles(req) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + for name, urlPath := range urlPaths { + req.PostForm.Set(name, urlPath) + } + + // 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} + fieldOrderValue := make(map[string]map[string][]string) + ordVal := make(map[string][]string) + for k, v := range req.PostForm { + if strings.Contains(k, ".") { + fo := strings.Split(k, ".") + + // put the order and the field value into map + field := string(fo[0]) + order := string(fo[1]) + fieldOrderValue[field] = ordVal + + // orderValue is 0:[?type=Thing&id=1] + orderValue := fieldOrderValue[field] + orderValue[order] = v + fieldOrderValue[field] = orderValue + + // discard the post form value with name.N + req.PostForm.Del(k) + } + + } + + // add/set the key & value to the post form in order + for f, ov := range fieldOrderValue { + for i := 0; i < len(ov); i++ { + position := fmt.Sprintf("%d", i) + fieldValue := ov[position] + + if req.PostForm.Get(f) == "" { + for i, fv := range fieldValue { + if i == 0 { + req.PostForm.Set(f, fv) + } else { + req.PostForm.Add(f, fv) + } + } + } else { + for _, fv := range fieldValue { + req.PostForm.Add(f, fv) + } + } + } + } + + 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) + return + } + + 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" + } + + http.Redirect(res, req, redir, http.StatusFound) + case http.MethodPut: + urlPaths, err := upload.StoreFiles(req) + if err != nil { + log.Println("Couldn't store file uploads.", err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "application/json") + res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) + default: + res.WriteHeader(http.StatusMethodNotAllowed) + return + } +} + +/* func editUploadHandler(res http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { res.WriteHeader(http.StatusMethodNotAllowed) @@ -2072,6 +2316,7 @@ func editUploadHandler(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "application/json") res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) } +*/ func searchHandler(res http.ResponseWriter, req *http.Request) { q := req.URL.Query() diff --git a/system/admin/server.go b/system/admin/server.go index 94aca78..3ee44f9 100644 --- a/system/admin/server.go +++ b/system/admin/server.go @@ -33,6 +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/contents", user.Auth(contentsHandler)) http.HandleFunc("/admin/contents/search", user.Auth(searchHandler)) diff --git a/system/admin/upload/upload.go b/system/admin/upload/upload.go index 35fb8f9..a0568e4 100644 --- a/system/admin/upload/upload.go +++ b/system/admin/upload/upload.go @@ -115,7 +115,7 @@ func storeFileInfo(size int64, filename, urlPath string, fds []*multipart.FileHe "content_length": []string{fmt.Sprintf("%d", size)}, } - err := db.SetUpload(data) + _, err := db.SetUpload("__uploads:-1", data) if err != nil { log.Println("Error saving file upload record to database:", err) } diff --git a/system/db/upload.go b/system/db/upload.go index cef4595..b05f505 100644 --- a/system/db/upload.go +++ b/system/db/upload.go @@ -10,41 +10,64 @@ import ( "github.com/ponzu-cms/ponzu/system/item" + "strconv" + "github.com/boltdb/bolt" "github.com/gorilla/schema" uuid "github.com/satori/go.uuid" ) // SetUpload stores information about files uploaded to the system -func SetUpload(data url.Values) error { - // set new UUID for upload - data.Set("uuid", uuid.NewV4().String()) +func SetUpload(target string, data url.Values) (int, error) { + parts := strings.Split(target, ":") + if parts[0] != "__uploads" { + return 0, fmt.Errorf("cannot call SetUpload with target type: %s", parts[0]) + } + pid := parts[1] - // create slug based on filename and timestamp/updated fields - slug := data.Get("name") - slug, err := checkSlugForDuplicate(slug) - if err != nil { - return err + if data.Get("uuid") == "" { + // set new UUID for upload + data.Set("uuid", uuid.NewV4().String()) + } + + if data.Get("slug") == "" { + // create slug based on filename and timestamp/updated fields + slug := data.Get("name") + slug, err := checkSlugForDuplicate(slug) + if err != nil { + return 0, err + } + data.Set("slug", slug) } - data.Set("slug", slug) ts := fmt.Sprintf("%d", time.Now().Unix()*1000) - data.Set("timestamp", ts) + if data.Get("timestamp") == "" { + data.Set("timestamp", ts) + } + data.Set("updated", ts) // store in database - err = store.Update(func(tx *bolt.Tx) error { + var id int64 + err := store.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte("__uploads")) if err != nil { return err } - // get sequential ID for item - id, err := b.NextSequence() - if err != nil { - return err + if pid == "-1" { + // get sequential ID for item + id, err := b.NextSequence() + if err != nil { + return err + } + data.Set("id", fmt.Sprintf("%d", id)) + } else { + id, err = strconv.ParseInt(pid, 10, 64) + if err != nil { + return err + } } - data.Set("id", fmt.Sprintf("%d", id)) file := &item.FileUpload{} dec := schema.NewDecoder() @@ -78,12 +101,14 @@ func SetUpload(data url.Values) error { if err != nil { return err } - // - return nil }) + if err != nil { + return 0, err + } - return err + return int(id), nil } // Upload returns the value for an upload by its target (__uploads:{id}) @@ -136,3 +161,34 @@ 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 +// } diff --git a/system/item/file.go b/system/item/file.go deleted file mode 100644 index 3b33b04..0000000 --- a/system/item/file.go +++ /dev/null @@ -1,96 +0,0 @@ -package item - -import ( - "fmt" - - "github.com/ponzu-cms/ponzu/management/editor" -) - -// FileUpload represents the file uploaded to the system -type FileUpload struct { - Item - - Name string `json:"name"` - Path string `json:"path"` - ContentLength int64 `json:"content_length"` - ContentType string `json:"content_type"` -} - -// String partially implements item.Identifiable and overrides Item's String() -func (f *FileUpload) String() string { return f.Name } - -// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable -func (f *FileUpload) MarshalEditor() ([]byte, error) { - view, err := editor.Form(f, - editor.Field{ - View: []byte(` -
    - - -

    ` + f.Name + `

    - - -

    File information:

    -
      -
    • Content-Length: ` + fmt.Sprintf("%d", f.ContentLength) + `
    • -
    • Content-Type: ` + f.ContentType + `
    • -
    -
    - `), - }, - ) - if err != nil { - return nil, err - } - - open := []byte(` -
    -
    -
    File Uploads
    -
    -
    - `) - close := []byte(`
    `) - script := []byte(` - - `) - - view = append(open, view...) - view = append(view, close...) - view = append(view, script...) - - return view, nil -} - -func (f *FileUpload) Push() []string { - return []string{ - "path", - } -} diff --git a/system/item/upload.go b/system/item/upload.go new file mode 100644 index 0000000..9253fcc --- /dev/null +++ b/system/item/upload.go @@ -0,0 +1,138 @@ +package item + +import ( + "fmt" + + "github.com/ponzu-cms/ponzu/management/editor" +) + +// FileUpload represents the file uploaded to the system +type FileUpload struct { + Item + + Name string `json:"name"` + Path string `json:"path"` + ContentLength int64 `json:"content_length"` + ContentType string `json:"content_type"` +} + +// String partially implements item.Identifiable and overrides Item's String() +func (f *FileUpload) String() string { return f.Name } + +// MarshalEditor writes a buffer of html to edit a Post and partially implements editor.Editable +func (f *FileUpload) MarshalEditor() ([]byte, error) { + view, err := editor.Form(f, + editor.Field{ + View: func() []byte { + if f.Path == "" { + return nil + } + + return []byte(` +
    + + +

    ` + f.Name + `

    + + +

    File information:

    +
      +
    • Content-Length: ` + fmt.Sprintf("%s", FmtBytes(float64(f.ContentLength))) + `
    • +
    • Content-Type: ` + f.ContentType + `
    • +
    +
    + `) + }(), + }, + editor.Field{ + View: editor.File("Path", f, map[string]string{ + "label": "File Upload", + "placeholder": "Upload the file here", + }), + }, + ) + if err != nil { + return nil, err + } + + open := []byte(` +
    +
    +
    File Uploads
    +
    +
    + `) + close := []byte(`
    `) + script := []byte(` + + `) + + view = append(open, view...) + view = append(view, close...) + view = append(view, script...) + + return view, nil +} + +func (f *FileUpload) Push() []string { + return []string{ + "path", + } +} + +// FmtBytes converts the numeric byte size value to the appropriate magnitude +// size in KB, MB, GB, TB, PB, or EB. +func FmtBytes(size float64) string { + unit := float64(1024) + BYTE := unit + KBYTE := BYTE * unit + MBYTE := KBYTE * unit + GBYTE := MBYTE * unit + TBYTE := GBYTE * unit + PBYTE := TBYTE * unit + + switch { + case size < BYTE: + return fmt.Sprintf("%0.f B", size) + case size < KBYTE: + return fmt.Sprintf("%.1f KB", size/BYTE) + case size < MBYTE: + return fmt.Sprintf("%.1f MB", size/KBYTE) + case size < GBYTE: + return fmt.Sprintf("%.1f GB", size/MBYTE) + case size < TBYTE: + return fmt.Sprintf("%.1f TB", size/GBYTE) + case size < PBYTE: + return fmt.Sprintf("%.1f PB", size/TBYTE) + default: + return fmt.Sprintf("%0.f B", size) + } + +} -- cgit v1.2.3 From 3c8c848606b996e2c7a06331401e622f888b84c5 Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Sat, 29 Apr 2017 22:43:44 -0500 Subject: adding search, edit/new, and list view for uploads --- system/admin/handlers.go | 185 ++++++++++++++++++++++++++++++++++------------- system/admin/server.go | 2 +- system/api/handlers.go | 2 +- system/db/upload.go | 109 ++++++++++++++++------------ system/item/upload.go | 40 +++++----- 5 files changed, 222 insertions(+), 116 deletions(-) (limited to 'system') 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) {
    -
    +
    search @@ -1073,7 +1073,7 @@ func uploadContentsHandler(res http.ResponseWriter, req *http.Request) { ` - btn := `
    ` + btn := `
    ` 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 := `` + i.String() + `` + if strings.HasPrefix(typeName, "__") { + link = `` + i.String() + `` + } + post := `
  • - ` + i.String() + ` + ` + link + ` Updated: ` + updatedTime + ` ` + publishTime + ` @@ -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 := `
    +
    +
    +
    Uploads Results
    + +
    + + search + + +
    + +
    +
      ` + + 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 := `
    • Error decoding data. Possible file corruption.
    • ` + _, 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(`
    `) + if err != nil { + log.Println(err) + + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + log.Println(err) + } + + res.Write(errView) + return + } + + btn := `
  • ` + 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(`
    - -

    ` + f.Name + `

    - - -

    File information:

    +
    ` + f.Name + `
      -
    • Content-Length: ` + fmt.Sprintf("%s", FmtBytes(float64(f.ContentLength))) + `
    • -
    • Content-Type: ` + f.ContentType + `
    • +
    • Content-Length: ` + fmt.Sprintf("%s", FmtBytes(float64(f.ContentLength))) + `
    • +
    • Content-Type: ` + f.ContentType + `
    • +
    • Uploaded: ` + FmtTime(f.Timestamp) + `
    `) @@ -55,17 +53,13 @@ func (f *FileUpload) MarshalEditor() ([]byte, error) { return nil, err } - open := []byte(` -
    -
    -
    File Uploads
    -
    -
    - `) - close := []byte(`
    `) script := []byte(` `) - 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)" +} -- cgit v1.2.3 From 8a1d091a0c6d55dbd6c713b2240bee8550541e7b Mon Sep 17 00:00:00 2001 From: Steve Manuel Date: Sat, 29 Apr 2017 23:01:51 -0500 Subject: add delete procedure and implementation for uploads --- system/admin/handlers.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ system/admin/server.go | 1 + system/db/upload.go | 22 +++++++++++++++++ 3 files changed, 87 insertions(+) (limited to 'system') diff --git a/system/admin/handlers.go b/system/admin/handlers.go index ac18020..bb36e39 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -2061,6 +2061,70 @@ func deleteHandler(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, redir, http.StatusFound) } +func deleteUploadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + res.WriteHeader(http.StatusMethodNotAllowed) + return + } + + err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + id := req.FormValue("id") + t := "__uploads" + + if id == "" || t == "" { + res.WriteHeader(http.StatusBadRequest) + return + } + + post := interface{}(&item.FileUpload{}) + hook, ok := post.(item.Hookable) + if !ok { + log.Println("Type", t, "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.BeforeDelete(res, req) + if err != nil { + log.Println("Error running BeforeDelete method in deleteHandler for:", t, err) + return + } + + err = db.DeleteUpload(t + ":" + id) + if err != nil { + log.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + err = hook.AfterDelete(res, req) + if err != nil { + log.Println("Error running AfterDelete method in deleteHandler for:", t, err) + return + } + + redir := "/admin/uploads" + http.Redirect(res, req, redir, http.StatusFound) +} + func editUploadHandler(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 b029be4..2180441 100644 --- a/system/admin/server.go +++ b/system/admin/server.go @@ -42,6 +42,7 @@ func Run() { http.HandleFunc("/admin/edit/delete", user.Auth(deleteHandler)) http.HandleFunc("/admin/edit/approve", user.Auth(approveContentHandler)) http.HandleFunc("/admin/edit/upload", user.Auth(editUploadHandler)) + http.HandleFunc("/admin/edit/upload/delete", user.Auth(deleteUploadHandler)) pwd, err := os.Getwd() if err != nil { diff --git a/system/db/upload.go b/system/db/upload.go index 956b7f9..beeee2d 100644 --- a/system/db/upload.go +++ b/system/db/upload.go @@ -201,6 +201,28 @@ func UploadAll() [][]byte { return uploads } +// DeleteUpload removes the value for an upload at its key id, based on the +// target provided i.e. __uploads:{id} +func DeleteUpload(target string) error { + parts := strings.Split(target, ":") + if len(parts) < 2 { + return fmt.Errorf("Error deleting upload, invalid target %s", target) + } + id, err := key(parts[1]) + if err != nil { + return err + } + + return store.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(parts[0])) + if b == nil { + return bolt.ErrBucketNotFound + } + + return b.Delete(id) + }) +} + func key(sid string) ([]byte, error) { id, err := strconv.Atoi(sid) if err != nil { -- cgit v1.2.3