Fix more linting issues and add tests

pull/25/head
Jan-Lukas Else 7 months ago
parent 94dba7a9b8
commit ffa4ba4ee0
  1. 4
      .golangci.yml
  2. 60
      activityPub.go
  3. 8
      blogstats_test.go
  4. 10
      cache.go
  5. 4
      commentsAdmin.go
  6. 12
      garbagecollector.go
  7. 2
      go.mod
  8. 3
      go.sum
  9. 2
      indexnow.go
  10. 78
      micropub.go
  11. 3
      micropubMedia.go
  12. 78
      micropub_test.go
  13. 4
      notifications.go
  14. 11
      posts.go
  15. 6
      templateAssets.go
  16. 22
      ui.go
  17. 10
      utils.go
  18. 3
      webmentionAdmin.go

@ -36,4 +36,6 @@ linters-settings:
checks: ["all"]
gostatichcheck:
go: "1.17"
checks: ["all"]
checks: ["all"]
dupl:
threshold: 125

@ -164,54 +164,44 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
case "Follow":
a.apAccept(blogName, blog, activity)
case "Undo":
{
if object, ok := activity["object"].(map[string]interface{}); ok {
ot := cast.ToString(object["type"])
actor := cast.ToString(object["actor"])
if ot == "Follow" && actor == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
if object, ok := activity["object"].(map[string]interface{}); ok {
ot := cast.ToString(object["type"])
actor := cast.ToString(object["actor"])
if ot == "Follow" && actor == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
}
case "Create":
{
if object, ok := activity["object"].(map[string]interface{}); ok {
baseUrl := cast.ToString(object["id"])
if ou := cast.ToString(object["url"]); ou != "" {
baseUrl = ou
}
if r := cast.ToString(object["inReplyTo"]); r != "" && baseUrl != "" && strings.HasPrefix(r, blogIri) {
// It's an ActivityPub reply; save reply as webmention
_ = a.createWebmention(baseUrl, r)
} else if content := cast.ToString(object["content"]); content != "" && baseUrl != "" {
// May be a mention; find links to blog and save them as webmentions
if links, err := allLinksFromHTMLString(content, baseUrl); err == nil {
for _, link := range links {
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
_ = a.createWebmention(baseUrl, link)
}
if object, ok := activity["object"].(map[string]interface{}); ok {
baseUrl := cast.ToString(object["id"])
if ou := cast.ToString(object["url"]); ou != "" {
baseUrl = ou
}
if r := cast.ToString(object["inReplyTo"]); r != "" && baseUrl != "" && strings.HasPrefix(r, blogIri) {
// It's an ActivityPub reply; save reply as webmention
_ = a.createWebmention(baseUrl, r)
} else if content := cast.ToString(object["content"]); content != "" && baseUrl != "" {
// May be a mention; find links to blog and save them as webmentions
if links, err := allLinksFromHTMLString(content, baseUrl); err == nil {
for _, link := range links {
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
_ = a.createWebmention(baseUrl, link)
}
}
}
}
}
case "Delete", "Block":
{
if o := cast.ToString(activity["object"]); o == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
if o := cast.ToString(activity["object"]); o == activityActor {
_ = a.db.apRemoveFollower(blogName, activityActor)
}
case "Like":
{
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, o))
}
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, o))
}
case "Announce":
{
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, o))
}
if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) {
a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, o))
}
}
// Return 200

@ -112,14 +112,12 @@ func Test_blogStats(t *testing.T) {
// Test HTML
t.Run("Test stats table", func(t *testing.T) {
h := http.HandlerFunc(app.serveBlogStatsTable)
req := httptest.NewRequest(http.MethodGet, "/abc", nil)
req = req.WithContext(context.WithValue(req.Context(), blogKey, "en"))
rec := httptest.NewRecorder()
h(rec, req)
app.serveBlogStatsTable(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)
@ -132,14 +130,12 @@ func Test_blogStats(t *testing.T) {
})
t.Run("Test stats page", func(t *testing.T) {
h := http.HandlerFunc(app.serveBlogStats)
req := httptest.NewRequest(http.MethodGet, "/abc", nil)
req = req.WithContext(context.WithValue(req.Context(), blogKey, "en"))
rec := httptest.NewRecorder()
h(rec, req)
app.serveBlogStats(rec, req)
res := rec.Result()
resBody, _ := io.ReadAll(res.Body)

@ -68,12 +68,10 @@ func (a *goBlog) cacheMiddleware(next http.Handler) http.Handler {
if cli, ok := r.Context().Value(cacheLoggedInKey).(bool); ok && cli {
// Continue caching, but remove login
setLoggedIn(r, false)
} else {
if a.isLoggedIn(r) {
// Don't cache logged in requests
next.ServeHTTP(w, r)
return
}
} else if a.isLoggedIn(r) {
// Don't cache logged in requests
next.ServeHTTP(w, r)
return
}
// Search and serve cache
key := cacheKey(r)

@ -37,10 +37,8 @@ func (p *commentsPaginationAdapter) Slice(offset, length int, data interface{})
func (a *goBlog) commentsAdmin(w http.ResponseWriter, r *http.Request) {
commentsPath := r.Context().Value(pathKey).(string)
// Adapter
pageNoString := chi.URLParam(r, "page")
pageNo, _ := strconv.Atoi(pageNoString)
p := paginator.New(&commentsPaginationAdapter{config: &commentsRequestConfig{}, db: a.db}, 5)
p.SetPage(pageNo)
p.SetPage(stringToInt(chi.URLParam(r, "page")))
var comments []*comment
err := p.Results(&comments)
if err != nil {

@ -17,14 +17,14 @@ func initGC() {
}
func doGC() {
var old, new runtime.MemStats
runtime.ReadMemStats(&old)
var before, after runtime.MemStats
runtime.ReadMemStats(&before)
runtime.GC()
runtime.ReadMemStats(&new)
runtime.ReadMemStats(&after)
log.Println(fmt.Sprintf(
"\nAlloc: %d MiB -> %d MiB\nSys: %d MiB -> %d MiB\nNumGC: %d",
old.Alloc/1024/1024, new.Alloc/1024/1024,
old.Sys/1024/1024, new.Sys/1024/1024,
new.NumGC,
before.Alloc/1024/1024, after.Alloc/1024/1024,
before.Sys/1024/1024, after.Sys/1024/1024,
after.NumGC,
))
}

@ -57,7 +57,7 @@ require (
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

@ -562,8 +562,9 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

@ -41,7 +41,7 @@ func (a *goBlog) indexNowEnabled() bool {
return true
}
func (a *goBlog) serveIndexNow(w http.ResponseWriter, r *http.Request) {
func (a *goBlog) serveIndexNow(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(a.indexNowKey())
}

@ -8,35 +8,29 @@ import (
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"github.com/spf13/cast"
"github.com/thoas/go-funk"
"go.goblog.app/app/pkgs/bufferpool"
"go.goblog.app/app/pkgs/contenttype"
"gopkg.in/yaml.v3"
)
const micropubPath = "/micropub"
type micropubConfig struct {
MediaEndpoint string `json:"media-endpoint,omitempty"`
}
func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
switch r.URL.Query().Get("q") {
var result interface{}
switch query := r.URL.Query(); query.Get("q") {
case "config":
w.Header().Set(contentType, contenttype.JSONUTF8)
mw := a.min.Writer(contenttype.JSON, w)
defer mw.Close()
_ = json.NewEncoder(mw).Encode(&micropubConfig{
MediaEndpoint: a.getFullAddress(micropubPath + micropubMediaSubPath),
})
type micropubConfig struct {
MediaEndpoint string `json:"media-endpoint"`
}
result = micropubConfig{MediaEndpoint: a.getFullAddress(micropubPath + micropubMediaSubPath)}
case "source":
var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" {
u, err := url.Parse(r.URL.Query().Get("url"))
if urlString := query.Get("url"); urlString != "" {
u, err := url.Parse(query.Get("url"))
if err != nil {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
@ -46,13 +40,11 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
mf = a.postToMfItem(p)
result = a.postToMfItem(p)
} else {
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
posts, err := a.getPosts(&postsRequestConfig{
limit: limit,
offset: offset,
limit: stringToInt(query.Get("limit")),
offset: stringToInt(query.Get("offset")),
})
if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
@ -62,12 +54,8 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
for _, p := range posts {
list["items"] = append(list["items"], a.postToMfItem(p))
}
mf = list
result = list
}
w.Header().Set(contentType, contenttype.JSONUTF8)
mw := a.min.Writer(contenttype.JSON, w)
defer mw.Close()
_ = json.NewEncoder(mw).Encode(mf)
case "category":
allCategories := []string{}
for blog := range a.cfg.Blogs {
@ -78,22 +66,28 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
allCategories = append(allCategories, values...)
}
w.Header().Set(contentType, contenttype.JSONUTF8)
mw := a.min.Writer(contenttype.JSON, w)
defer mw.Close()
_ = json.NewEncoder(mw).Encode(map[string]interface{}{
"categories": allCategories,
})
result = map[string]interface{}{"categories": allCategories}
default:
a.serve404(w, r)
return
}
buf := bufferpool.Get()
defer bufferpool.Put(buf)
mw := a.min.Writer(contenttype.JSON, buf)
err := json.NewEncoder(mw).Encode(result)
_ = mw.Close()
if err != nil {
a.serveError(w, r, "Failed to encode json", http.StatusInternalServerError)
return
}
w.Header().Set(contentType, contenttype.JSONUTF8)
_, _ = buf.WriteTo(w)
}
func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
switch mt, _, _ := mime.ParseMediaType(r.Header.Get(contentType)); mt {
case contenttype.WWWForm, contenttype.MultipartForm:
_ = r.ParseForm()
_ = r.ParseMultipartForm(0)
if r.Form == nil {
a.serveError(w, r, "Failed to parse form", http.StatusBadRequest)
@ -442,9 +436,16 @@ func (a *goBlog) micropubCreatePostFromJson(w http.ResponseWriter, r *http.Reque
a.micropubCreate(w, r, p)
}
func (a *goBlog) micropubCheckScope(w http.ResponseWriter, r *http.Request, required string) bool {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), required) {
a.serveError(w, r, required+" scope missing", http.StatusForbidden)
return false
}
return true
}
func (a *goBlog) micropubCreate(w http.ResponseWriter, r *http.Request, p *post) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "create") {
a.serveError(w, r, "create scope missing", http.StatusForbidden)
if !a.micropubCheckScope(w, r, "create") {
return
}
if err := a.computeExtraPostParameters(p); err != nil {
@ -459,8 +460,7 @@ func (a *goBlog) micropubCreate(w http.ResponseWriter, r *http.Request, p *post)
}
func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u string) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "delete") {
a.serveError(w, r, "delete scope missing", http.StatusForbidden)
if !a.micropubCheckScope(w, r, "delete") {
return
}
uu, err := url.Parse(u)
@ -476,8 +476,7 @@ func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u string
}
func (a *goBlog) micropubUndelete(w http.ResponseWriter, r *http.Request, u string) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "undelete") {
a.serveError(w, r, "undelete scope missing", http.StatusForbidden)
if !a.micropubCheckScope(w, r, "undelete") {
return
}
uu, err := url.Parse(u)
@ -493,8 +492,7 @@ func (a *goBlog) micropubUndelete(w http.ResponseWriter, r *http.Request, u stri
}
func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u string, mf *microformatItem) {
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "update") {
a.serveError(w, r, "update scope missing", http.StatusForbidden)
if !a.micropubCheckScope(w, r, "update") {
return
}
uu, err := url.Parse(u)

@ -17,8 +17,7 @@ const micropubMediaSubPath = "/media"
func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
// Check scope
if !strings.Contains(r.Context().Value(indieAuthScope).(string), "media") {
a.serveError(w, r, "media scope missing", http.StatusForbidden)
if !a.micropubCheckScope(w, r, "media") {
return
}
// Check if request is multipart

@ -0,0 +1,78 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_micropubQuery(t *testing.T) {
app := &goBlog{
cfg: createDefaultTestConfig(t),
}
_ = app.initConfig()
_ = app.initDatabase(false)
defer app.db.close()
app.initComponents(false)
// Create a test post with tags
err := app.createPost(&post{
Path: "/test/post",
Content: "Test post",
Parameters: map[string][]string{
"tags": {"test", "test2"},
},
})
require.NoError(t, err)
type testCase struct {
query string
want string
wantStatus int
}
testCases := []testCase{
{
query: "config",
want: "{\"media-endpoint\":\"http://localhost:8080/micropub/media\"}\n",
wantStatus: http.StatusOK,
},
{
query: "source&url=http://localhost:8080/test/post",
want: "{\"type\":[\"h-entry\"],\"properties\":{\"published\":[\"\"],\"updated\":[\"\"],\"post-status\":[\"published\"],\"visibility\":[\"public\"],\"category\":[\"test\",\"test2\"],\"content\":[\"---\\nblog: default\\npath: /test/post\\npriority: 0\\npublished: \\\"\\\"\\nsection: \\\"\\\"\\nstatus: published\\ntags:\\n - test\\n - test2\\nupdated: \\\"\\\"\\n---\\nTest post\"],\"url\":[\"http://localhost:8080/test/post\"],\"mp-slug\":[\"\"]}}\n",
wantStatus: http.StatusOK,
},
{
query: "source",
want: "{\"items\":[{\"type\":[\"h-entry\"],\"properties\":{\"published\":[\"\"],\"updated\":[\"\"],\"post-status\":[\"published\"],\"visibility\":[\"public\"],\"category\":[\"test\",\"test2\"],\"content\":[\"---\\nblog: default\\npath: /test/post\\npriority: 0\\npublished: \\\"\\\"\\nsection: \\\"\\\"\\nstatus: published\\ntags:\\n - test\\n - test2\\nupdated: \\\"\\\"\\n---\\nTest post\"],\"url\":[\"http://localhost:8080/test/post\"],\"mp-slug\":[\"\"]}}]}\n",
wantStatus: http.StatusOK,
},
{
query: "category",
want: "{\"categories\":[\"test\",\"test2\"]}\n",
wantStatus: http.StatusOK,
},
{
query: "somethingelse",
wantStatus: http.StatusNotFound,
},
}
for _, tc := range testCases {
req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/micropub?q="+tc.query, nil)
rec := httptest.NewRecorder()
app.serveMicropubQuery(rec, req)
rec.Flush()
assert.Equal(t, tc.wantStatus, rec.Code)
if tc.want != "" {
assert.Equal(t, tc.want, rec.Body.String())
}
}
}

@ -122,10 +122,8 @@ func (p *notificationsPaginationAdapter) Slice(offset, length int, data interfac
func (a *goBlog) notificationsAdmin(w http.ResponseWriter, r *http.Request) {
// Adapter
pageNoString := chi.URLParam(r, "page")
pageNo, _ := strconv.Atoi(pageNoString)
p := paginator.New(&notificationsPaginationAdapter{config: &notificationsRequestConfig{}, db: a.db}, 10)
p.SetPage(pageNo)
p.SetPage(stringToInt(chi.URLParam(r, "page")))
var notifications []*notification
err := p.Results(&notifications)
if err != nil {

@ -6,7 +6,6 @@ import (
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
"time"
@ -202,13 +201,13 @@ func (a *goBlog) serveDeleted(w http.ResponseWriter, r *http.Request) {
func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
var year, month, day int
if ys := chi.URLParam(r, "year"); ys != "" && ys != "x" {
year, _ = strconv.Atoi(ys)
year = stringToInt(ys)
}
if ms := chi.URLParam(r, "month"); ms != "" && ms != "x" {
month, _ = strconv.Atoi(ms)
month = stringToInt(ms)
}
if ds := chi.URLParam(r, "day"); ds != "" {
day, _ = strconv.Atoi(ds)
day = stringToInt(ds)
}
if year == 0 && month == 0 && day == 0 {
a.serve404(w, r)
@ -270,8 +269,6 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
// Decode and sanitize search
search = cleanHTMLText(searchDecode(search))
}
pageNoString := chi.URLParam(r, "page")
pageNo, _ := strconv.Atoi(pageNoString)
var sections []string
if ic.section != nil {
sections = []string{ic.section.Name}
@ -300,7 +297,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
statusse: statusse,
priorityOrder: true,
}, a: a}, bc.Pagination)
p.SetPage(pageNo)
p.SetPage(stringToInt(chi.URLParam(r, "page")))
var posts []*post
err := p.Results(&posts)
if err != nil {

@ -122,9 +122,5 @@ func (a *goBlog) initChromaCSS() error {
if err != nil {
return err
}
err = a.compileAsset(chromaPath, buf)
if err != nil {
return err
}
return nil
return a.compileAsset(chromaPath, buf)
}

22
ui.go

@ -471,21 +471,13 @@ func (a *goBlog) renderBlogStatsTable(hb *htmlBuilder, rd *renderData) {
hb.writeElementOpen("th", "class", "tar")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "posts"))
hb.writeElementClose("th")
// Chars
hb.writeElementOpen("th", "class", "tar")
hb.write("~")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "chars"))
hb.writeElementClose("th")
// Words
hb.writeElementOpen("th", "class", "tar")
hb.write("~")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "words"))
hb.writeElementClose("th")
// Words/post
hb.writeElementOpen("th", "class", "tar")
hb.write("~")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "wordsperpost"))
hb.writeElementClose("th")
// Chars, Words, Words/Post
for _, s := range []string{"chars", "words", "wordsperpost"} {
hb.writeElementOpen("th", "class", "tar")
hb.write("~")
hb.writeEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, s))
hb.writeElementClose("th")
}
hb.writeElementClose("thead")
// Table body
hb.writeElementOpen("tbody")

@ -12,6 +12,7 @@ import (
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -211,9 +212,7 @@ func urlHasExt(rawUrl string, allowed ...string) (ext string, has bool) {
return "", false
}
ext = ext[1:]
allowed = funk.Map(allowed, func(str string) string {
return strings.ToLower(str)
}).([]string)
allowed = funk.Map(allowed, strings.ToLower).([]string)
return ext, funk.ContainsString(allowed, strings.ToLower(ext))
}
@ -375,3 +374,8 @@ func matchTimeDiffLocale(lang string) tdl.Locale {
timeDiffLocaleMap[lang] = locale
return locale
}
func stringToInt(s string) int {
i, _ := strconv.Atoi(s)
return i
}

@ -38,7 +38,6 @@ func (p *webmentionPaginationAdapter) Slice(offset, length int, data interface{}
}
func (a *goBlog) webmentionAdmin(w http.ResponseWriter, r *http.Request) {
pageNo, _ := strconv.Atoi(chi.URLParam(r, "page"))
var status webmentionStatus = ""
switch webmentionStatus(r.URL.Query().Get("status")) {
case webmentionStatusVerified:
@ -51,7 +50,7 @@ func (a *goBlog) webmentionAdmin(w http.ResponseWriter, r *http.Request) {
status: status,
sourcelike: sourcelike,
}, db: a.db}, 5)
p.SetPage(pageNo)
p.SetPage(stringToInt(chi.URLParam(r, "page")))
var mentions []*mention
err := p.Results(&mentions)
if err != nil {

Loading…
Cancel
Save