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 --- management/editor/elements.go | 2 +- 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 ++++++++++++++++++++++++ 7 files changed, 460 insertions(+), 116 deletions(-) delete mode 100644 system/item/file.go create mode 100644 system/item/upload.go diff --git a/management/editor/elements.go b/management/editor/elements.go index b179960..2dfab40 100644 --- a/management/editor/elements.go +++ b/management/editor/elements.go @@ -276,7 +276,7 @@ func Richtext(fieldName string, p interface{}, attrs map[string]string) []byte { data.append("file", files[0]); $.ajax({ data: data, - type: 'POST', + type: 'PUT', url: '/admin/edit/upload', cache: false, contentType: false, 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:

- -
- `), - }, - ) - 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:

+ +
+ `) + }(), + }, + 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