From 175c78624b4ecdfe0fd83edb63808df76f2b5232 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 17 Feb 2021 16:07:42 +0100 Subject: [PATCH] Comments Admin pagination --- comments.go | 42 ++++++------- commentsAdmin.go | 98 +++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- http.go | 4 +- posts.go | 4 -- templates/commentsadmin.gohtml | 8 ++- webmention.go | 107 -------------------------------- webmentionAdmin.go | 109 +++++++++++++++++++++++++++++++++ 9 files changed, 241 insertions(+), 137 deletions(-) create mode 100644 commentsAdmin.go create mode 100644 webmentionAdmin.go diff --git a/comments.go b/comments.go index fdc80e4..1670de8 100644 --- a/comments.go +++ b/comments.go @@ -104,20 +104,24 @@ func checkCommentTarget(w http.ResponseWriter, r *http.Request) string { return targetURL.Path } -func commentsAdmin(w http.ResponseWriter, r *http.Request) { - comments, err := getComments() - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - render(w, templateCommentsAdmin, &renderData{ - Data: comments, - }) +type commentsRequestConfig struct { + offset, limit int } -func getComments() ([]*comment, error) { +func buildCommentsQuery(config *commentsRequestConfig) (query string, args []interface{}) { + args = []interface{}{} + query = "select id, target, name, website, comment from comments order by id desc" + if config.limit != 0 || config.offset != 0 { + query += " limit @limit offset @offset" + args = append(args, sql.Named("limit", config.limit), sql.Named("offset", config.offset)) + } + return +} + +func getComments(config *commentsRequestConfig) ([]*comment, error) { comments := []*comment{} - rows, err := appDbQuery("select id, target, name, website, comment from comments order by id desc") + query, args := buildCommentsQuery(config) + rows, err := appDbQuery(query, args...) if err != nil { return nil, err } @@ -132,19 +136,15 @@ func getComments() ([]*comment, error) { return comments, nil } -func commentsAdminDelete(w http.ResponseWriter, r *http.Request) { - id, err := strconv.Atoi(r.FormValue("commentid")) +func countComments(config *commentsRequestConfig) (count int, err error) { + query, params := buildCommentsQuery(config) + query = "select count(*) from (" + query + ")" + row, err := appDbQueryRow(query, params...) if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) return } - err = deleteComment(id) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - purgeCache() - http.Redirect(w, r, ".", http.StatusFound) + err = row.Scan(&count) + return } func deleteComment(id int) error { diff --git a/commentsAdmin.go b/commentsAdmin.go new file mode 100644 index 0000000..8384273 --- /dev/null +++ b/commentsAdmin.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "net/http" + "reflect" + "strconv" + + "github.com/go-chi/chi" + "github.com/vcraescu/go-paginator" +) + +type commentsPaginationAdapter struct { + config *commentsRequestConfig + nums int64 +} + +func (p *commentsPaginationAdapter) Nums() (int64, error) { + if p.nums == 0 { + nums, _ := countComments(p.config) + p.nums = int64(nums) + } + return p.nums, nil +} + +func (p *commentsPaginationAdapter) Slice(offset, length int, data interface{}) error { + modifiedConfig := *p.config + modifiedConfig.offset = offset + modifiedConfig.limit = length + + comments, err := getComments(&modifiedConfig) + reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&comments).Elem()) + return err +} + +func commentsAdmin(blog, commentPath string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + // Adapter + pageNoString := chi.URLParam(r, "page") + pageNo, _ := strconv.Atoi(pageNoString) + p := paginator.New(&commentsPaginationAdapter{config: &commentsRequestConfig{}}, 5) + p.SetPage(pageNo) + var comments []*comment + err := p.Results(&comments) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } + // Navigation + var hasPrev, hasNext bool + var prevPage, nextPage int + var prevPath, nextPath string + hasPrev, _ = p.HasPrev() + if hasPrev { + prevPage, _ = p.PrevPage() + } else { + prevPage, _ = p.Page() + } + if prevPage < 2 { + prevPath = commentPath + } else { + prevPath = fmt.Sprintf("%s/page/%d", commentPath, prevPage) + } + hasNext, _ = p.HasNext() + if hasNext { + nextPage, _ = p.NextPage() + } else { + nextPage, _ = p.Page() + } + nextPath = fmt.Sprintf("%s/page/%d", commentPath, nextPage) + // Render + render(w, templateCommentsAdmin, &renderData{ + BlogString: blog, + Data: map[string]interface{}{ + "Comments": comments, + "HasPrev": hasPrev, + "HasNext": hasNext, + "Prev": slashIfEmpty(prevPath), + "Next": slashIfEmpty(nextPath), + }, + }) + } +} + +func commentsAdminDelete(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(r.FormValue("commentid")) + if err != nil { + serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + err = deleteComment(id) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } + purgeCache() + http.Redirect(w, r, ".", http.StatusFound) +} diff --git a/go.mod b/go.mod index 85b07b7..1283dfc 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( golang.org/x/mod v0.4.1 // indirect golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20210216224549-f992740a1bac // indirect + golang.org/x/sys v0.0.0-20210217105451-b926d437f341 // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/text v0.3.5 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 299b167..6313862 100644 --- a/go.sum +++ b/go.sum @@ -404,8 +404,8 @@ golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210216224549-f992740a1bac h1:9glrpwtNjBYgRpb67AZJKHfzj1stG/8BL5H7In2oTC4= -golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210217105451-b926d437f341 h1:2/QtM1mL37YmcsT8HaDNHDgTqqFVw+zr8UzMiBVLzYU= +golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= diff --git a/http.go b/http.go index b8572e9..60a21f2 100644 --- a/http.go +++ b/http.go @@ -366,7 +366,9 @@ func buildHandler() (http.Handler, error) { // Admin cr.Group(func(r chi.Router) { r.Use(authMiddleware) - r.Get("/", commentsAdmin) + handler := commentsAdmin(blog, commentsPath) + r.Get("/", handler) + r.Get(paginationPath, handler) r.Post("/delete", commentsAdminDelete) }) }) diff --git a/posts.go b/posts.go index 6cb99fd..5c70c46 100644 --- a/posts.go +++ b/posts.go @@ -98,10 +98,6 @@ func (p *postPaginationAdapter) Nums() (int64, error) { } func (p *postPaginationAdapter) Slice(offset, length int, data interface{}) error { - if reflect.TypeOf(data).Kind() != reflect.Ptr { - panic("data has to be a pointer") - } - modifiedConfig := *p.config modifiedConfig.offset = offset modifiedConfig.limit = length diff --git a/templates/commentsadmin.gohtml b/templates/commentsadmin.gohtml index 5673cdf..101f0d6 100644 --- a/templates/commentsadmin.gohtml +++ b/templates/commentsadmin.gohtml @@ -6,7 +6,7 @@

{{ string .Blog.Lang "comments" }}

{{ $blog := .Blog }} - {{ range $i, $comment := .Data }} + {{ range $i, $comment := .Data.Comments }}

Target: {{ $comment.Target }}
@@ -20,6 +20,12 @@

{{ end }} + {{ if .Data.HasPrev }} +

{{ string .Blog.Lang "prev" }}

+ {{ end }} + {{ if .Data.HasNext }} +

{{ string .Blog.Lang "next" }}

+ {{ end }}
{{ end }} diff --git a/webmention.go b/webmention.go index cd665b1..b222b12 100644 --- a/webmention.go +++ b/webmention.go @@ -6,13 +6,8 @@ import ( "fmt" "net/http" "net/http/httptest" - "reflect" - "strconv" "strings" "time" - - "github.com/go-chi/chi" - "github.com/vcraescu/go-paginator" ) type webmentionStatus string @@ -87,81 +82,6 @@ func extractMention(r *http.Request) (*mention, error) { }, nil } -func webmentionAdmin(w http.ResponseWriter, r *http.Request) { - pageNoString := chi.URLParam(r, "page") - pageNo, _ := strconv.Atoi(pageNoString) - p := paginator.New(&webmentionPaginationAdapter{config: &webmentionsRequestConfig{}}, 10) - p.SetPage(pageNo) - var mentions []*mention - err := p.Results(&mentions) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - // Navigation - var hasPrev, hasNext bool - var prevPage, nextPage int - var prevPath, nextPath string - hasPrev, _ = p.HasPrev() - if hasPrev { - prevPage, _ = p.PrevPage() - } else { - prevPage, _ = p.Page() - } - if prevPage < 2 { - prevPath = webmentionPath - } else { - prevPath = fmt.Sprintf("%s/page/%d", webmentionPath, prevPage) - } - hasNext, _ = p.HasNext() - if hasNext { - nextPage, _ = p.NextPage() - } else { - nextPage, _ = p.Page() - } - nextPath = fmt.Sprintf("%s/page/%d", webmentionPath, nextPage) - // Render - render(w, "webmentionadmin", &renderData{ - Data: map[string]interface{}{ - "Mentions": mentions, - "HasPrev": hasPrev, - "HasNext": hasNext, - "Prev": slashIfEmpty(prevPath), - "Next": slashIfEmpty(nextPath), - }, - }) -} - -func webmentionAdminDelete(w http.ResponseWriter, r *http.Request) { - id, err := strconv.Atoi(r.FormValue("mentionid")) - if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) - return - } - err = deleteWebmention(id) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - purgeCache() - http.Redirect(w, r, ".", http.StatusFound) -} - -func webmentionAdminApprove(w http.ResponseWriter, r *http.Request) { - id, err := strconv.Atoi(r.FormValue("mentionid")) - if err != nil { - serveError(w, r, err.Error(), http.StatusBadRequest) - return - } - err = approveWebmention(id) - if err != nil { - serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - purgeCache() - http.Redirect(w, r, ".", http.StatusFound) -} - func webmentionExists(source, target string) bool { result := 0 row, err := appDbQueryRow("select exists(select 1 from webmentions where source = ? and target = ?)", source, target) @@ -199,33 +119,6 @@ type webmentionsRequestConfig struct { offset, limit int } -type webmentionPaginationAdapter struct { - config *webmentionsRequestConfig - nums int64 -} - -func (p *webmentionPaginationAdapter) Nums() (int64, error) { - if p.nums == 0 { - nums, _ := countWebmentions(p.config) - p.nums = int64(nums) - } - return p.nums, nil -} - -func (p *webmentionPaginationAdapter) Slice(offset, length int, data interface{}) error { - if reflect.TypeOf(data).Kind() != reflect.Ptr { - panic("data has to be a pointer") - } - - modifiedConfig := *p.config - modifiedConfig.offset = offset - modifiedConfig.limit = length - - wms, err := getWebmentions(&modifiedConfig) - reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&wms).Elem()) - return err -} - func buildWebmentionsQuery(config *webmentionsRequestConfig) (query string, args []interface{}) { args = []interface{}{} filter := "" diff --git a/webmentionAdmin.go b/webmentionAdmin.go new file mode 100644 index 0000000..451d6b3 --- /dev/null +++ b/webmentionAdmin.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "net/http" + "reflect" + "strconv" + + "github.com/go-chi/chi" + "github.com/vcraescu/go-paginator" +) + +type webmentionPaginationAdapter struct { + config *webmentionsRequestConfig + nums int64 +} + +func (p *webmentionPaginationAdapter) Nums() (int64, error) { + if p.nums == 0 { + nums, _ := countWebmentions(p.config) + p.nums = int64(nums) + } + return p.nums, nil +} + +func (p *webmentionPaginationAdapter) Slice(offset, length int, data interface{}) error { + modifiedConfig := *p.config + modifiedConfig.offset = offset + modifiedConfig.limit = length + + wms, err := getWebmentions(&modifiedConfig) + reflect.ValueOf(data).Elem().Set(reflect.ValueOf(&wms).Elem()) + return err +} + +func webmentionAdmin(w http.ResponseWriter, r *http.Request) { + pageNoString := chi.URLParam(r, "page") + pageNo, _ := strconv.Atoi(pageNoString) + p := paginator.New(&webmentionPaginationAdapter{config: &webmentionsRequestConfig{}}, 10) + p.SetPage(pageNo) + var mentions []*mention + err := p.Results(&mentions) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } + // Navigation + var hasPrev, hasNext bool + var prevPage, nextPage int + var prevPath, nextPath string + hasPrev, _ = p.HasPrev() + if hasPrev { + prevPage, _ = p.PrevPage() + } else { + prevPage, _ = p.Page() + } + if prevPage < 2 { + prevPath = webmentionPath + } else { + prevPath = fmt.Sprintf("%s/page/%d", webmentionPath, prevPage) + } + hasNext, _ = p.HasNext() + if hasNext { + nextPage, _ = p.NextPage() + } else { + nextPage, _ = p.Page() + } + nextPath = fmt.Sprintf("%s/page/%d", webmentionPath, nextPage) + // Render + render(w, "webmentionadmin", &renderData{ + Data: map[string]interface{}{ + "Mentions": mentions, + "HasPrev": hasPrev, + "HasNext": hasNext, + "Prev": slashIfEmpty(prevPath), + "Next": slashIfEmpty(nextPath), + }, + }) +} + +func webmentionAdminDelete(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(r.FormValue("mentionid")) + if err != nil { + serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + err = deleteWebmention(id) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } + purgeCache() + http.Redirect(w, r, ".", http.StatusFound) +} + +func webmentionAdminApprove(w http.ResponseWriter, r *http.Request) { + id, err := strconv.Atoi(r.FormValue("mentionid")) + if err != nil { + serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + err = approveWebmention(id) + if err != nil { + serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } + purgeCache() + http.Redirect(w, r, ".", http.StatusFound) +}