mirror of https://github.com/jlelse/GoBlog
Fix more linting issues and add tests
This commit is contained in:
parent
94dba7a9b8
commit
ffa4ba4ee0
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
10
cache.go
10
cache.go
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
2
go.mod
|
@ -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
3
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
78
micropub.go
78
micropub.go
|
@ -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(µpubConfig{
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
func (a *goBlog) notificationsAdmin(w http.ResponseWriter, r *http.Request) {
|
||||||
// Adapter
|
// Adapter
|
||||||
pageNoString := chi.URLParam(r, "page")
|
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
|
||||||
p := paginator.New(¬ificationsPaginationAdapter{config: ¬ificationsRequestConfig{}, db: a.db}, 10)
|
p := paginator.New(¬ificationsPaginationAdapter{config: ¬ificationsRequestConfig{}, db: a.db}, 10)
|
||||||
p.SetPage(pageNo)
|
p.SetPage(stringToInt(chi.URLParam(r, "page")))
|
||||||
var notifications []*notification
|
var notifications []*notification
|
||||||
err := p.Results(¬ifications)
|
err := p.Results(¬ifications)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
11
posts.go
11
posts.go
|
@ -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 {
|
||||||
|
|
|
@ -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
22
ui.go
|
@ -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")
|
||||||
|
|
10
utils.go
10
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue