Add pagination to Webmention Admin

This commit is contained in:
Jan-Lukas Else 2021-01-30 19:37:26 +01:00
parent a840879d48
commit 78f3d3c07b
3 changed files with 113 additions and 44 deletions

10
http.go
View File

@ -71,6 +71,10 @@ func reloadRouter() error {
} }
func buildHandler() (http.Handler, error) { func buildHandler() (http.Handler, error) {
paginationPath := "/page/{page:[0-9-]+}"
feedPath := ".{feed:rss|json|atom}"
r := chi.NewRouter() r := chi.NewRouter()
if appConfig.Server.Logging { if appConfig.Server.Logging {
@ -128,10 +132,11 @@ func buildHandler() (http.Handler, error) {
} }
// Webmentions // Webmentions
r.Route("/webmention", func(webmentionRouter chi.Router) { r.Route(webmentionPath, func(webmentionRouter chi.Router) {
webmentionRouter.Use(middleware.NoCache) webmentionRouter.Use(middleware.NoCache)
webmentionRouter.Post("/", handleWebmention) webmentionRouter.Post("/", handleWebmention)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin) webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get(paginationPath, webmentionAdmin)
webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete) webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete)
webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove) webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove)
}) })
@ -194,9 +199,6 @@ func buildHandler() (http.Handler, error) {
// Short paths // Short paths
r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath) r.With(cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", redirectToLongPath)
paginationPath := "/page/{page:[0-9-]+}"
feedPath := ".{feed:rss|json|atom}"
for blog, blogConfig := range appConfig.Blogs { for blog, blogConfig := range appConfig.Blogs {
fullBlogPath := blogConfig.Path fullBlogPath := blogConfig.Path

View File

@ -5,9 +5,8 @@
{{ define "main" }} {{ define "main" }}
<main> <main>
<h1>{{ string .Blog.Lang "webmentions" }}</h1> <h1>{{ string .Blog.Lang "webmentions" }}</h1>
<h2>{{ string .Blog.Lang "verified" }}</h2>
{{ $blog := .Blog }} {{ $blog := .Blog }}
{{ range $i, $mention := .Data.Verified }} {{ range $i, $mention := .Data.Mentions }}
<div class="p"> <div class="p">
<p> <p>
From: <a href="{{ $mention.Source }}" target="_blank" rel="noopener noreferrer">{{ $mention.Source }}</a><br/> From: <a href="{{ $mention.Source }}" target="_blank" rel="noopener noreferrer">{{ $mention.Source }}</a><br/>
@ -16,24 +15,18 @@
</p> </p>
<form method="post"> <form method="post">
<input type="hidden" name="mentionid" value="{{ $mention.ID }}"> <input type="hidden" name="mentionid" value="{{ $mention.ID }}">
<input type="submit" formaction="/webmention/approve" value="{{ string $blog.Lang "approve" }}"> {{ if eq $mention.Status "verified" }}
<input type="submit" formaction="/webmention/approve" value="{{ string $blog.Lang "approve" }}">
{{ end }}
<input type="submit" formaction="/webmention/delete" value="{{ string $blog.Lang "delete" }}"> <input type="submit" formaction="/webmention/delete" value="{{ string $blog.Lang "delete" }}">
</form> </form>
</div> </div>
{{ end }} {{ end }}
<h2>{{ string .Blog.Lang "approved" }}</h2> {{ if .Data.HasPrev }}
{{ range $i, $mention := .Data.Approved }} <p><a href="{{ .Data.Prev }}">{{ string .Blog.Lang "prev" }}</a></p>
<div class="p"> {{ end }}
<p> {{ if .Data.HasNext }}
From: <a href="{{ $mention.Source }}" target="_blank" rel="noopener noreferrer">{{ $mention.Source }}</a><br/> <p><a href="{{ .Data.Next }}">{{ string .Blog.Lang "next" }}</a></p>
To: <a href="{{ $mention.Target }}" target="_blank">{{ $mention.Target }}</a><br/>
Created: {{ unixtodate $mention.Created }}
</p>
<form method="post">
<input type="hidden" name="mentionid" value="{{ $mention.ID }}">
<input type="submit" formaction="/webmention/delete" value="{{ string $blog.Lang "delete" }}">
</form>
</div>
{{ end }} {{ end }}
</main> </main>
{{ end }} {{ end }}

View File

@ -6,18 +6,22 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/go-chi/chi"
"github.com/vcraescu/go-paginator"
) )
type webmentionStatus string type webmentionStatus string
const ( const (
webmentionStatusNew webmentionStatus = "new"
webmentionStatusRenew webmentionStatus = "renew"
webmentionStatusVerified webmentionStatus = "verified" webmentionStatusVerified webmentionStatus = "verified"
webmentionStatusApproved webmentionStatus = "approved" webmentionStatusApproved webmentionStatus = "approved"
webmentionPath = "/webmention"
) )
type mention struct { type mention struct {
@ -28,6 +32,7 @@ type mention struct {
Title string Title string
Content string Content string
Author string Author string
Status webmentionStatus
} }
func initWebmention() error { func initWebmention() error {
@ -83,24 +88,46 @@ func extractMention(r *http.Request) (*mention, error) {
} }
func webmentionAdmin(w http.ResponseWriter, r *http.Request) { func webmentionAdmin(w http.ResponseWriter, r *http.Request) {
verified, err := getWebmentions(&webmentionsRequestConfig{ pageNoString := chi.URLParam(r, "page")
status: webmentionStatusVerified, pageNo, _ := strconv.Atoi(pageNoString)
}) p := paginator.New(&webmentionPaginationAdapter{config: &webmentionsRequestConfig{}}, 10)
p.SetPage(pageNo)
var mentions []*mention
err := p.Results(&mentions)
if err != nil { if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError) serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
} }
approved, err := getWebmentions(&webmentionsRequestConfig{ // Navigation
status: webmentionStatusApproved, var hasPrev, hasNext bool
}) var prevPage, nextPage int
if err != nil { var prevPath, nextPath string
serveError(w, r, err.Error(), http.StatusInternalServerError) hasPrev, _ = p.HasPrev()
return 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{ render(w, "webmentionadmin", &renderData{
Data: map[string][]*mention{ Data: map[string]interface{}{
"Verified": verified, "Mentions": mentions,
"Approved": approved, "HasPrev": hasPrev,
"HasNext": hasNext,
"Prev": slashIfEmpty(prevPath),
"Next": slashIfEmpty(nextPath),
}, },
}) })
} }
@ -168,16 +195,41 @@ func approveWebmention(id int) error {
} }
type webmentionsRequestConfig struct { type webmentionsRequestConfig struct {
target string target string
status webmentionStatus status webmentionStatus
asc bool asc bool
offset, limit int
} }
func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) { type webmentionPaginationAdapter struct {
mentions := []*mention{} config *webmentionsRequestConfig
var rows *sql.Rows nums int64
var err error }
args := []interface{}{}
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 := "" filter := ""
if config != nil { if config != nil {
if config.target != "" && config.status != "" { if config.target != "" && config.status != "" {
@ -195,13 +247,24 @@ func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
if config.asc { if config.asc {
order = "asc" order = "asc"
} }
rows, err = appDbQuery("select id, source, target, created, title, content, author from webmentions "+filter+" order by created "+order, args...) query = "select id, source, target, created, title, content, author, status from webmentions " + filter + " order by created " + order
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 query, args
}
func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
mentions := []*mention{}
query, args := buildWebmentionsQuery(config)
rows, err := appDbQuery(query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for rows.Next() { for rows.Next() {
m := &mention{} m := &mention{}
err = rows.Scan(&m.ID, &m.Source, &m.Target, &m.Created, &m.Title, &m.Content, &m.Author) err = rows.Scan(&m.ID, &m.Source, &m.Target, &m.Created, &m.Title, &m.Content, &m.Author, &m.Status)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,3 +272,14 @@ func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
} }
return mentions, nil return mentions, nil
} }
func countWebmentions(config *webmentionsRequestConfig) (count int, err error) {
query, params := buildWebmentionsQuery(config)
query = "select count(*) from (" + query + ")"
row, err := appDbQueryRow(query, params...)
if err != nil {
return
}
err = row.Scan(&count)
return
}