summaryrefslogtreecommitdiff
path: root/system
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2017-05-15 12:18:06 -0700
committerGitHub <noreply@github.com>2017-05-15 12:18:06 -0700
commitf176e7d98f0facbf0ccfac08eb4d7aea03c53c8b (patch)
treef11aa7e399e354d8f613d18ea10f14cc8e405773 /system
parent0cf8aa550a3da63cb1509678bf5add0d73925546 (diff)
parent0ee16cc099c4fea518e9e57ff4b5166aba54ee34 (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.go142
-rw-r--r--system/admin/handlers.go43
-rw-r--r--system/admin/server.go1
-rw-r--r--system/admin/static/dashboard/css/admin.css2
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;
}