diff options
author | Steve Manuel <nilslice@gmail.com> | 2017-05-15 12:18:06 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-15 12:18:06 -0700 |
commit | f176e7d98f0facbf0ccfac08eb4d7aea03c53c8b (patch) | |
tree | f11aa7e399e354d8f613d18ea10f14cc8e405773 /system | |
parent | 0cf8aa550a3da63cb1509678bf5add0d73925546 (diff) | |
parent | 0ee16cc099c4fea518e9e57ff4b5166aba54ee34 (diff) |
Merge pull request #142 from ponzu-cms/ponzu-dev
[core] add CSVFormattable interface to export CSV formatted content
Diffstat (limited to 'system')
-rw-r--r-- | system/admin/export.go | 142 | ||||
-rw-r--r-- | system/admin/handlers.go | 43 | ||||
-rw-r--r-- | system/admin/server.go | 1 | ||||
-rw-r--r-- | system/admin/static/dashboard/css/admin.css | 2 |
4 files changed, 183 insertions, 5 deletions
diff --git a/system/admin/export.go b/system/admin/export.go new file mode 100644 index 0000000..2453eb8 --- /dev/null +++ b/system/admin/export.go @@ -0,0 +1,142 @@ +package admin + +import ( + "encoding/csv" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/ponzu-cms/ponzu/management/format" + "github.com/ponzu-cms/ponzu/system/db" + "github.com/ponzu-cms/ponzu/system/item" + + "github.com/tidwall/gjson" +) + +func exportHandler(res http.ResponseWriter, req *http.Request) { + // /admin/contents/export?type=Blogpost&format=csv + q := req.URL.Query() + t := q.Get("type") + f := strings.ToLower(q.Get("format")) + + if t == "" || f == "" { + v, err := Error400() + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.WriteHeader(http.StatusBadRequest) + _, err = res.Write(v) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + + } + + pt, ok := item.Types[t] + if !ok { + res.WriteHeader(http.StatusBadRequest) + return + } + + switch f { + case "csv": + csv, ok := pt().(format.CSVFormattable) + if !ok { + res.WriteHeader(http.StatusBadRequest) + return + } + + fields := csv.FormatCSV() + exportCSV(res, req, pt, fields) + + default: + res.WriteHeader(http.StatusBadRequest) + return + } +} + +func exportCSV(res http.ResponseWriter, req *http.Request, pt func() interface{}, fields []string) { + tmpFile, err := ioutil.TempFile(os.TempDir(), "exportcsv-") + if err != nil { + log.Println("Failed to create tmp file for CSV export:", err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + err = os.Chmod(tmpFile.Name(), 0666) + if err != nil { + log.Println("chmod err:", err) + } + + csvBuf := csv.NewWriter(tmpFile) + + t := req.URL.Query().Get("type") + + // get content data and loop through creating a csv row per result + bb := db.ContentAll(t) + + // add field names to first row + err = csvBuf.Write(fields) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + log.Println("Failed to write column headers:", fields) + return + } + + for row := range bb { + // unmarshal data and loop over fields + rowBuf := []string{} + + for _, col := range fields { + // pull out each field as the column value + result := gjson.GetBytes(bb[row], col) + + // append it to the buffer + rowBuf = append(rowBuf, result.String()) + } + + // write row to csv + err := csvBuf.Write(rowBuf) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + log.Println("Failed to write column headers:", fields) + return + } + } + + csvBuf.Flush() + + // write the buffer to a content-disposition response + fi, err := tmpFile.Stat() + if err != nil { + log.Println("Failed to read tmp file info for CSV export:", err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + err = tmpFile.Close() + if err != nil { + log.Println("Failed to close tmp file for CSV export:", err) + } + + ts := time.Now().Unix() + disposition := `attachment; filename="export-%s-%d.csv"` + + res.Header().Set("Content-Type", "text/csv") + res.Header().Set("Content-Disposition", fmt.Sprintf(disposition, t, ts)) + res.Header().Set("Content-Length", fmt.Sprintf("%d", int(fi.Size()))) + + http.ServeFile(res, req, tmpFile.Name()) + + err = os.Remove(tmpFile.Name()) + if err != nil { + log.Println("Failed to remove tmp file for CSV export:", err) + } +} diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 23c7e50..a950633 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ponzu-cms/ponzu/management/editor" + "github.com/ponzu-cms/ponzu/management/format" "github.com/ponzu-cms/ponzu/management/manager" "github.com/ponzu-cms/ponzu/system/addon" "github.com/ponzu-cms/ponzu/system/admin/config" @@ -1492,8 +1493,20 @@ func contentsHandler(res http.ResponseWriter, req *http.Request) { </script> ` - btn := `<div class="col s3"><a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">New ` + t + `</a></div></div>` - html = html + b.String() + script + btn + btn := `<div class="col s3"> + <a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light"> + New ` + t + ` + </a>` + + if _, ok := pt.(format.CSVFormattable); ok { + btn += `<br/> + <a href="/admin/contents/export?type=` + t + `&format=csv" class="green darken-4 btn export-post waves-effect waves-light"> + <i class="material-icons left">system_update_alt</i> + CSV + </a>` + } + + html += b.String() + script + btn + `</div></div>` adminView, err := Admin([]byte(html)) if err != nil { @@ -2422,7 +2435,15 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { posts := db.ContentAll(t + specifier) b := &bytes.Buffer{} - p := item.Types[t]().(editor.Editable) + pt, ok := item.Types[t] + if !ok { + res.WriteHeader(http.StatusBadRequest) + return + } + + post := pt() + + p := post.(editor.Editable) html := `<div class="col s9 card"> <div class="card-content"> @@ -2499,9 +2520,23 @@ func searchHandler(res http.ResponseWriter, req *http.Request) { return } - btn := `<div class="col s3"><a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">New ` + t + `</a></div></div>` + btn := `<div class="col s3"> + <a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light"> + New ` + t + ` + </a>` html = html + b.String() + btn + if _, ok := post.(format.CSVFormattable); ok { + btn = `<br/> + <a href="/admin/contents/export?type=` + t + `&format=csv" class="green darken-4 btn export-post waves-effect waves-light"> + <i class="material-icons left">system_update_alt</i> + CSV + </a>` + html = html + b.String() + btn + } + + html += `</div></div>` + adminView, err := Admin([]byte(html)) if err != nil { log.Println(err) diff --git a/system/admin/server.go b/system/admin/server.go index 2180441..426dabd 100644 --- a/system/admin/server.go +++ b/system/admin/server.go @@ -37,6 +37,7 @@ func Run() { http.HandleFunc("/admin/contents", user.Auth(contentsHandler)) http.HandleFunc("/admin/contents/search", user.Auth(searchHandler)) + http.HandleFunc("/admin/contents/export", user.Auth(exportHandler)) http.HandleFunc("/admin/edit", user.Auth(editHandler)) http.HandleFunc("/admin/edit/delete", user.Auth(deleteHandler)) diff --git a/system/admin/static/dashboard/css/admin.css b/system/admin/static/dashboard/css/admin.css index 06df137..45ca5d8 100644 --- a/system/admin/static/dashboard/css/admin.css +++ b/system/admin/static/dashboard/css/admin.css @@ -81,7 +81,7 @@ li a:hover { transition: color 0.3s ease; } -a.new-post { +a.new-post, a.export-post { margin: 0.5rem 0 1rem 0.75rem; } |