Fix more linting issues and add tests

This commit is contained in:
Jan-Lukas Else 2022-02-26 20:38:52 +01:00
parent 94dba7a9b8
commit ffa4ba4ee0
18 changed files with 183 additions and 137 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
go.mod
View File

@ -57,7 +57,7 @@ require (
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38 github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 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/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

3
go.sum
View File

@ -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-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-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-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-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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

View File

@ -41,7 +41,7 @@ func (a *goBlog) indexNowEnabled() bool {
return true 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()) _, _ = w.Write(a.indexNowKey())
} }

View File

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

View File

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

78
micropub_test.go Normal file
View File

@ -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())
}
}
}

View File

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

View File

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

View File

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

22
ui.go
View File

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

View File

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

View File

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