summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2017-04-25 13:23:37 -0700
committerSteve Manuel <nilslice@gmail.com>2017-04-25 13:23:37 -0700
commit099d000119447708d7d0d0482758d352438fa7e5 (patch)
treef4386ae2ff25a5b6b15c2e6442d4c56705e8271e
parent7092fb8979869f3c09b364d454d8d8081bb7c0bc (diff)
adding support for file upload type and API handler to fetch file info
-rw-r--r--system/admin/upload/upload.go25
-rw-r--r--system/api/handlers.go41
-rw-r--r--system/api/server.go4
-rw-r--r--system/db/init.go5
-rw-r--r--system/db/upload.go142
-rw-r--r--system/item/file.go96
-rw-r--r--system/system.go (renamed from system/auth.go)0
7 files changed, 309 insertions, 4 deletions
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/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(`
+ <div class="input-field col s12">
+ <label class="active">{{ .Name }}</label>
+ <!-- Add your custom editor field view here. -->
+ <h4>` + f.Name + `</h4>
+
+ <img class="preview" src="` + f.Path + `"/>
+ <p>File information:</p>
+ <ul>
+ <li>Content-Length: ` + fmt.Sprintf("%d", f.ContentLength) + `</li>
+ <li>Content-Type: ` + f.ContentType + `</li>
+ </ul>
+ </div>
+ `),
+ },
+ )
+ if err != nil {
+ 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() {
+ // hide default fields & labels unnecessary for the config
+ var fields = $('.default-fields');
+ fields.css('position', 'relative');
+ fields.find('input:not([type=submit])').remove();
+ fields.find('label').remove();
+ fields.find('button').css({
+ position: 'absolute',
+ top: '-10px',
+ right: '0px'
+ });
+
+ var contentOnly = $('.content-only.__ponzu');
+ contentOnly.hide();
+ contentOnly.find('input, textarea, select').attr('name', '');
+
+ // adjust layout of td so save button is in same location as usual
+ fields.find('td').css('float', 'right');
+
+ // stop some fixed config settings from being modified
+ fields.find('input[name=client_secret]').attr('name', '');
+
+ // hide save, show delete
+ fields.find('.save-post').hide();
+ fields.find('.delete-post').show();
+ });
+ </script>
+ `)
+
+ 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/auth.go b/system/system.go
index 8b12ab5..8b12ab5 100644
--- a/system/auth.go
+++ b/system/system.go