mirror of https://github.com/jlelse/GoBlog
Reduce complexity of router build method
This commit is contained in:
parent
91e2b268c7
commit
88f26ef3c6
|
@ -22,7 +22,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) initActivityPub() error {
|
func (a *goBlog) initActivityPub() error {
|
||||||
if !a.cfg.ActivityPub.Enabled {
|
if a.isPrivate() {
|
||||||
|
// Private mode, no AP
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if apc := a.cfg.ActivityPub; apc == nil || !apc.Enabled {
|
||||||
|
// Disabled
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Add hooks
|
// Add hooks
|
||||||
|
|
|
@ -26,7 +26,7 @@ func (a *goBlog) checkActivityStreamsRequest(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
|
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled && !a.isPrivate() {
|
||||||
// Check if accepted media type is not HTML
|
// Check if accepted media type is not HTML
|
||||||
if mt, _, err := ct.GetAcceptableMediaType(r, a.asCheckMediaTypes); err == nil && mt.String() != a.asCheckMediaTypes[0].String() {
|
if mt, _, err := ct.GetAcceptableMediaType(r, a.asCheckMediaTypes); err == nil && mt.String() != a.asCheckMediaTypes[0].String() {
|
||||||
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), asRequestKey, true)))
|
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), asRequestKey, true)))
|
||||||
|
|
2
app.go
2
app.go
|
@ -45,8 +45,6 @@ type goBlog struct {
|
||||||
pUpdateHooks []postHookFunc
|
pUpdateHooks []postHookFunc
|
||||||
pDeleteHooks []postHookFunc
|
pDeleteHooks []postHookFunc
|
||||||
hourlyHooks []hourlyHookFunc
|
hourlyHooks []hourlyHookFunc
|
||||||
// HTTP
|
|
||||||
cspDomains string
|
|
||||||
// HTTP Client
|
// HTTP Client
|
||||||
httpClient httpClient
|
httpClient httpClient
|
||||||
// HTTP Routers
|
// HTTP Routers
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
const defaultBlogrollPath = "/blogroll"
|
const defaultBlogrollPath = "/blogroll"
|
||||||
|
|
||||||
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
return a.getBlogrollOutlines(blog)
|
return a.getBlogrollOutlines(blog)
|
||||||
})
|
})
|
||||||
|
@ -42,7 +42,7 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
return a.getBlogrollOutlines(blog)
|
return a.getBlogrollOutlines(blog)
|
||||||
})
|
})
|
||||||
|
|
11
blogstats.go
11
blogstats.go
|
@ -7,7 +7,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultBlogStatsPath = "/statistics"
|
const (
|
||||||
|
defaultBlogStatsPath = "/statistics"
|
||||||
|
blogStatsTablePath = ".table.html"
|
||||||
|
)
|
||||||
|
|
||||||
func (a *goBlog) initBlogStats() {
|
func (a *goBlog) initBlogStats() {
|
||||||
f := func(p *post) {
|
f := func(p *post) {
|
||||||
|
@ -19,20 +22,20 @@ func (a *goBlog) initBlogStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
bc := a.cfg.Blogs[blog]
|
bc := a.cfg.Blogs[blog]
|
||||||
canonical := bc.getRelativePath(defaultIfEmpty(bc.BlogStats.Path, defaultBlogStatsPath))
|
canonical := bc.getRelativePath(defaultIfEmpty(bc.BlogStats.Path, defaultBlogStatsPath))
|
||||||
a.render(w, r, templateBlogStats, &renderData{
|
a.render(w, r, templateBlogStats, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Canonical: a.getFullAddress(canonical),
|
Canonical: a.getFullAddress(canonical),
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"TableUrl": canonical + ".table.html",
|
"TableUrl": canonical + blogStatsTablePath,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
|
||||||
return a.db.getBlogStats(blog)
|
return a.db.getBlogStats(blog)
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (a *goBlog) serveComment(w http.ResponseWriter, r *http.Request) {
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
a.render(w, r, templateComment, &renderData{
|
a.render(w, r, templateComment, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Canonical: a.getFullAddress(a.cfg.Blogs[blog].getRelativePath(fmt.Sprintf("/comment/%d", id))),
|
Canonical: a.getFullAddress(a.cfg.Blogs[blog].getRelativePath(fmt.Sprintf("/comment/%d", id))),
|
||||||
|
@ -75,7 +75,7 @@ func (a *goBlog) createComment(w http.ResponseWriter, r *http.Request) {
|
||||||
// Serve error
|
// Serve error
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
} else {
|
} else {
|
||||||
commentAddress := fmt.Sprintf("%s/%d", a.getRelativePath(r.Context().Value(blogContextKey).(string), "/comment"), commentID)
|
commentAddress := fmt.Sprintf("%s/%d", a.getRelativePath(r.Context().Value(blogKey).(string), "/comment"), commentID)
|
||||||
// Send webmention
|
// Send webmention
|
||||||
_ = a.createWebmention(a.getFullAddress(commentAddress), a.getFullAddress(target))
|
_ = a.createWebmention(a.getFullAddress(commentAddress), a.getFullAddress(target))
|
||||||
// Redirect to comment
|
// Redirect to comment
|
||||||
|
|
|
@ -35,8 +35,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) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
commentsPath := r.Context().Value(pathContextKey).(string)
|
commentsPath := r.Context().Value(pathKey).(string)
|
||||||
// Adapter
|
// Adapter
|
||||||
pageNoString := chi.URLParam(r, "page")
|
pageNoString := chi.URLParam(r, "page")
|
||||||
pageNo, _ := strconv.Atoi(pageNoString)
|
pageNo, _ := strconv.Atoi(pageNoString)
|
||||||
|
|
|
@ -290,8 +290,6 @@ func (a *goBlog) initConfig() error {
|
||||||
viper.SetDefault("micropub.locationParam", "location")
|
viper.SetDefault("micropub.locationParam", "location")
|
||||||
viper.SetDefault("activityPub.keyPath", "data/private.pem")
|
viper.SetDefault("activityPub.keyPath", "data/private.pem")
|
||||||
viper.SetDefault("activityPub.tagsTaxonomies", []string{"tags"})
|
viper.SetDefault("activityPub.tagsTaxonomies", []string{"tags"})
|
||||||
viper.SetDefault("webmention.disableSending", false)
|
|
||||||
viper.SetDefault("webmention.disableReceiving", false)
|
|
||||||
// Unmarshal config
|
// Unmarshal config
|
||||||
a.cfg = &config{}
|
a.cfg = &config{}
|
||||||
err = viper.Unmarshal(a.cfg)
|
err = viper.Unmarshal(a.cfg)
|
||||||
|
@ -329,9 +327,6 @@ func (a *goBlog) initConfig() error {
|
||||||
}
|
}
|
||||||
a.cfg.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(a.cfg.Micropub.MediaStorage.MediaURL, "/")
|
a.cfg.Micropub.MediaStorage.MediaURL = strings.TrimSuffix(a.cfg.Micropub.MediaStorage.MediaURL, "/")
|
||||||
}
|
}
|
||||||
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
|
||||||
a.cfg.ActivityPub = &configActivityPub{Enabled: false}
|
|
||||||
}
|
|
||||||
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
||||||
// Disable comments for all blogs
|
// Disable comments for all blogs
|
||||||
for _, b := range a.cfg.Blogs {
|
for _, b := range a.cfg.Blogs {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
const defaultContactPath = "/contact"
|
const defaultContactPath = "/contact"
|
||||||
|
|
||||||
func (a *goBlog) serveContactForm(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveContactForm(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
cc := a.cfg.Blogs[blog].Contact
|
cc := a.cfg.Blogs[blog].Contact
|
||||||
a.render(w, r, templateContact, &renderData{
|
a.render(w, r, templateContact, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
|
@ -62,7 +62,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
_, _ = message.WriteString(formMessage)
|
_, _ = message.WriteString(formMessage)
|
||||||
// Send submission
|
// Send submission
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
if cc := a.cfg.Blogs[blog].Contact; cc != nil && cc.SMTPHost != "" && cc.EmailFrom != "" && cc.EmailTo != "" {
|
if cc := a.cfg.Blogs[blog].Contact; cc != nil && cc.SMTPHost != "" && cc.EmailFrom != "" && cc.EmailTo != "" {
|
||||||
// Build email
|
// Build email
|
||||||
var email bytes.Buffer
|
var email bytes.Buffer
|
||||||
|
|
|
@ -7,7 +7,7 @@ const customPageContextKey = "custompage"
|
||||||
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||||
page := r.Context().Value(customPageContextKey).(*configCustomPage)
|
page := r.Context().Value(customPageContextKey).(*configCustomPage)
|
||||||
a.render(w, r, page.Template, &renderData{
|
a.render(w, r, page.Template, &renderData{
|
||||||
BlogString: r.Context().Value(blogContextKey).(string),
|
BlogString: r.Context().Value(blogKey).(string),
|
||||||
Canonical: a.getFullAddress(page.Path),
|
Canonical: a.getFullAddress(page.Path),
|
||||||
Data: page.Data,
|
Data: page.Data,
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
const editorPath = "/editor"
|
const editorPath = "/editor"
|
||||||
|
|
||||||
func (a *goBlog) serveEditor(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveEditor(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
a.render(w, r, templateEditor, &renderData{
|
a.render(w, r, templateEditor, &renderData{
|
||||||
BlogString: blog,
|
BlogString: blog,
|
||||||
Data: map[string]interface{}{},
|
Data: map[string]interface{}{},
|
||||||
|
@ -22,7 +22,7 @@ func (a *goBlog) serveEditor(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
if action := r.FormValue("editoraction"); action != "" {
|
if action := r.FormValue("editoraction"); action != "" {
|
||||||
switch action {
|
switch action {
|
||||||
case "loaddelete":
|
case "loaddelete":
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
// Get files
|
// Get files
|
||||||
files, err := a.mediaFiles()
|
files, err := a.mediaFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -69,5 +69,5 @@ func (a *goBlog) serveEditorFilesDelete(w http.ResponseWriter, r *http.Request)
|
||||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, a.getRelativePath(r.Context().Value(blogContextKey).(string), "/editor/files"), http.StatusFound)
|
http.Redirect(w, r, a.getRelativePath(r.Context().Value(blogKey).(string), "/editor/files"), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
6
feeds.go
6
feeds.go
|
@ -41,16 +41,14 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
created, _ := dateparse.ParseLocal(p.Published)
|
|
||||||
updated, _ := dateparse.ParseLocal(p.Updated)
|
|
||||||
feed.Add(&feeds.Item{
|
feed.Add(&feeds.Item{
|
||||||
Title: p.Title(),
|
Title: p.Title(),
|
||||||
Link: &feeds.Link{Href: a.fullPostURL(p)},
|
Link: &feeds.Link{Href: a.fullPostURL(p)},
|
||||||
Description: a.postSummary(p),
|
Description: a.postSummary(p),
|
||||||
Id: p.Path,
|
Id: p.Path,
|
||||||
Content: string(a.postHtml(p, true)),
|
Content: string(a.postHtml(p, true)),
|
||||||
Created: created,
|
Created: timeNoErr(dateparse.ParseLocal(p.Published)),
|
||||||
Updated: updated,
|
Updated: timeNoErr(dateparse.ParseLocal(p.Updated)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
const defaultGeoMapPath = "/map"
|
const defaultGeoMapPath = "/map"
|
||||||
|
|
||||||
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
bc := a.cfg.Blogs[blog]
|
bc := a.cfg.Blogs[blog]
|
||||||
|
|
||||||
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -53,7 +53,7 @@ require (
|
||||||
// master
|
// master
|
||||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -465,8 +465,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
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=
|
||||||
|
|
393
http.go
393
http.go
|
@ -8,9 +8,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dchest/captcha"
|
"github.com/dchest/captcha"
|
||||||
|
@ -26,16 +24,18 @@ const (
|
||||||
contentType = "Content-Type"
|
contentType = "Content-Type"
|
||||||
userAgent = "User-Agent"
|
userAgent = "User-Agent"
|
||||||
appUserAgent = "GoBlog"
|
appUserAgent = "GoBlog"
|
||||||
|
|
||||||
|
blogKey contextKey = "blog"
|
||||||
|
pathKey contextKey = "httpPath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) startServer() (err error) {
|
func (a *goBlog) startServer() (err error) {
|
||||||
log.Println("Start server(s)...")
|
log.Println("Start server(s)...")
|
||||||
// Load router
|
// Load router
|
||||||
router, err := a.buildRouter()
|
a.d, err = a.buildRouter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.d = fixHTTPHandler(router)
|
|
||||||
// Set basic middlewares
|
// Set basic middlewares
|
||||||
h := alice.New()
|
h := alice.New()
|
||||||
if a.cfg.Server.Logging {
|
if a.cfg.Server.Logging {
|
||||||
|
@ -126,28 +126,21 @@ const (
|
||||||
feedPath = ".{feed:rss|json|atom}"
|
feedPath = ".{feed:rss|json|atom}"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) buildRouter() (*chi.Mux, error) {
|
func (a *goBlog) buildRouter() (http.Handler, error) {
|
||||||
r := chi.NewRouter()
|
r := chi.NewMux()
|
||||||
|
|
||||||
// Private mode
|
|
||||||
privateMode := false
|
|
||||||
var privateModeHandler []func(http.Handler) http.Handler
|
|
||||||
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
|
||||||
privateMode = true
|
|
||||||
privateModeHandler = append(privateModeHandler, a.authMiddleware)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic middleware
|
// Basic middleware
|
||||||
|
r.Use(fixHTTPHandler)
|
||||||
r.Use(a.redirectShortDomain)
|
r.Use(a.redirectShortDomain)
|
||||||
r.Use(middleware.RedirectSlashes)
|
r.Use(middleware.RedirectSlashes)
|
||||||
r.Use(middleware.CleanPath)
|
r.Use(middleware.CleanPath)
|
||||||
r.Use(middleware.GetHead)
|
r.Use(middleware.GetHead)
|
||||||
if !a.cfg.Cache.Enable {
|
if cache := a.cfg.Cache; cache != nil && !cache.Enable {
|
||||||
r.Use(middleware.NoCache)
|
r.Use(middleware.NoCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No Index Header
|
// No Index Header
|
||||||
if privateMode {
|
if a.isPrivate() {
|
||||||
r.Use(noIndexHeader)
|
r.Use(noIndexHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,340 +148,60 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
|
||||||
r.Use(a.checkIsLogin)
|
r.Use(a.checkIsLogin)
|
||||||
r.Use(a.checkIsCaptcha)
|
r.Use(a.checkIsCaptcha)
|
||||||
|
|
||||||
// Logout
|
// Login
|
||||||
r.With(a.authMiddleware).Get("/login", serveLogin)
|
r.Group(a.loginRouter)
|
||||||
r.With(a.authMiddleware).Get("/logout", a.serveLogout)
|
|
||||||
|
|
||||||
// Micropub
|
// Micropub
|
||||||
r.Route(micropubPath, func(r chi.Router) {
|
r.Route(micropubPath, a.micropubRouter)
|
||||||
r.Use(a.checkIndieAuth)
|
|
||||||
r.Get("/", a.serveMicropubQuery)
|
|
||||||
r.Post("/", a.serveMicropubPost)
|
|
||||||
r.Post(micropubMediaSubPath, a.serveMicropubMedia)
|
|
||||||
})
|
|
||||||
|
|
||||||
// IndieAuth
|
// IndieAuth
|
||||||
r.Route("/indieauth", func(r chi.Router) {
|
r.Route("/indieauth", a.indieAuthRouter)
|
||||||
r.Get("/", a.indieAuthRequest)
|
|
||||||
r.With(a.authMiddleware).Post("/accept", a.indieAuthAccept)
|
|
||||||
r.Post("/", a.indieAuthVerification)
|
|
||||||
r.Get("/token", a.indieAuthToken)
|
|
||||||
r.Post("/token", a.indieAuthToken)
|
|
||||||
})
|
|
||||||
|
|
||||||
// ActivityPub and stuff
|
// ActivityPub and stuff
|
||||||
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
|
r.Group(a.activityPubRouter)
|
||||||
r.Route("/activitypub", func(r chi.Router) {
|
|
||||||
r.Post("/inbox/{blog}", a.apHandleInbox)
|
|
||||||
r.Post("/{blog}/inbox", a.apHandleInbox)
|
|
||||||
})
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(cacheLoggedIn, a.cacheMiddleware)
|
|
||||||
r.Get("/.well-known/webfinger", a.apHandleWebfinger)
|
|
||||||
r.Get("/.well-known/host-meta", handleWellKnownHostMeta)
|
|
||||||
r.Get("/.well-known/nodeinfo", a.serveNodeInfoDiscover)
|
|
||||||
r.Get("/nodeinfo", a.serveNodeInfo)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webmentions
|
// Webmentions
|
||||||
if wm := a.cfg.Webmention; wm != nil && !wm.DisableReceiving {
|
r.Route(webmentionPath, a.webmentionsRouter)
|
||||||
r.Route(webmentionPath, func(r chi.Router) {
|
|
||||||
r.Post("/", a.handleWebmention)
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
// Authenticated routes
|
|
||||||
r.Use(a.authMiddleware)
|
|
||||||
r.Get("/", a.webmentionAdmin)
|
|
||||||
r.Get(paginationPath, a.webmentionAdmin)
|
|
||||||
r.Post("/delete", a.webmentionAdminDelete)
|
|
||||||
r.Post("/approve", a.webmentionAdminApprove)
|
|
||||||
r.Post("/reverify", a.webmentionAdminReverify)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
r.Route(notificationsPath, func(r chi.Router) {
|
r.Route(notificationsPath, a.notificationsRouter)
|
||||||
r.Use(a.authMiddleware)
|
|
||||||
r.Get("/", a.notificationsAdmin)
|
|
||||||
r.Get(paginationPath, a.notificationsAdmin)
|
|
||||||
r.Post("/delete", a.notificationsAdminDelete)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Assets
|
// Assets
|
||||||
for _, path := range a.allAssetPaths() {
|
r.Group(a.assetsRouter)
|
||||||
r.Get(path, a.serveAsset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static files
|
// Static files
|
||||||
for _, path := range allStaticPaths() {
|
r.Group(a.staticFilesRouter)
|
||||||
r.With(privateModeHandler...).Get(path, a.serveStaticFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Media files
|
// Media files
|
||||||
r.With(privateModeHandler...).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, a.serveMediaFile)
|
r.With(a.privateModeHandler).Get(`/m/{file:[0-9a-fA-F]+(\.[0-9a-zA-Z]+)?}`, a.serveMediaFile)
|
||||||
|
|
||||||
// Captcha
|
// Captcha
|
||||||
r.Handle("/captcha/*", captcha.Server(500, 250))
|
r.Handle("/captcha/*", captcha.Server(500, 250))
|
||||||
|
|
||||||
// Short paths
|
// Short paths
|
||||||
r.With(privateModeHandler...).With(cacheLoggedIn, a.cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", a.redirectToLongPath)
|
r.With(a.privateModeHandler, cacheLoggedIn, a.cacheMiddleware).Get("/s/{id:[0-9a-fA-F]+}", a.redirectToLongPath)
|
||||||
|
|
||||||
|
// Blogs
|
||||||
for blog, blogConfig := range a.cfg.Blogs {
|
for blog, blogConfig := range a.cfg.Blogs {
|
||||||
sbm := middleware.WithValue(blogContextKey, blog)
|
r.Group(a.blogRouter(blog, blogConfig))
|
||||||
|
|
||||||
// Sections
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware, sbm)
|
|
||||||
for _, section := range blogConfig.Sections {
|
|
||||||
if section.Name != "" {
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
secPath := blogConfig.getRelativePath(section.Name)
|
|
||||||
r.Use(middleware.WithValue(indexConfigKey, &indexConfig{
|
|
||||||
path: secPath,
|
|
||||||
section: section,
|
|
||||||
}))
|
|
||||||
r.Get(secPath, a.serveIndex)
|
|
||||||
r.Get(secPath+feedPath, a.serveIndex)
|
|
||||||
r.Get(secPath+paginationPath, a.serveIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Taxonomies
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware, sbm)
|
|
||||||
for _, taxonomy := range blogConfig.Taxonomies {
|
|
||||||
if taxonomy.Name != "" {
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(middleware.WithValue(taxonomyContextKey, taxonomy))
|
|
||||||
taxBasePath := blogConfig.getRelativePath(taxonomy.Name)
|
|
||||||
r.Get(taxBasePath, a.serveTaxonomy)
|
|
||||||
taxValPath := taxBasePath + "/{taxValue}"
|
|
||||||
r.Get(taxValPath, a.serveTaxonomyValue)
|
|
||||||
r.Get(taxValPath+feedPath, a.serveTaxonomyValue)
|
|
||||||
r.Get(taxValPath+paginationPath, a.serveTaxonomyValue)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Photos
|
|
||||||
if pc := blogConfig.Photos; pc != nil && pc.Enabled {
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
photoPath := blogConfig.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware, sbm, middleware.WithValue(indexConfigKey, &indexConfig{
|
|
||||||
path: photoPath,
|
|
||||||
parameter: pc.Parameter,
|
|
||||||
title: pc.Title,
|
|
||||||
description: pc.Description,
|
|
||||||
summaryTemplate: templatePhotosSummary,
|
|
||||||
}))
|
|
||||||
r.Get(photoPath, a.serveIndex)
|
|
||||||
r.Get(photoPath+feedPath, a.serveIndex)
|
|
||||||
r.Get(photoPath+paginationPath, a.serveIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search
|
|
||||||
if bsc := blogConfig.Search; bsc != nil && bsc.Enabled {
|
|
||||||
searchPath := blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath))
|
|
||||||
r.Route(searchPath, func(r chi.Router) {
|
|
||||||
r.Use(sbm, middleware.WithValue(
|
|
||||||
pathContextKey,
|
|
||||||
searchPath,
|
|
||||||
))
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware)
|
|
||||||
r.Get("/", a.serveSearch)
|
|
||||||
r.Post("/", a.serveSearch)
|
|
||||||
searchResultPath := "/" + searchPlaceholder
|
|
||||||
r.Get(searchResultPath, a.serveSearchResult)
|
|
||||||
r.Get(searchResultPath+feedPath, a.serveSearchResult)
|
|
||||||
r.Get(searchResultPath+paginationPath, a.serveSearchResult)
|
|
||||||
})
|
|
||||||
r.With(a.cacheMiddleware).Get("/opensearch.xml", a.serveOpenSearch)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
if bsc := blogConfig.BlogStats; bsc != nil && bsc.Enabled {
|
|
||||||
statsPath := blogConfig.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.With(a.cacheMiddleware, sbm).Get(statsPath, a.serveBlogStats)
|
|
||||||
r.With(cacheLoggedIn, a.cacheMiddleware, sbm).Get(statsPath+".table.html", a.serveBlogStatsTable)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date archives
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware, sbm)
|
|
||||||
|
|
||||||
yearRegex := `/{year:x|\d\d\d\d}`
|
|
||||||
monthRegex := `/{month:x|\d\d}`
|
|
||||||
dayRegex := `/{day:\d\d}`
|
|
||||||
|
|
||||||
yearPath := blogConfig.getRelativePath(yearRegex)
|
|
||||||
r.Get(yearPath, a.serveDate)
|
|
||||||
r.Get(yearPath+feedPath, a.serveDate)
|
|
||||||
r.Get(yearPath+paginationPath, a.serveDate)
|
|
||||||
|
|
||||||
monthPath := yearPath + monthRegex
|
|
||||||
r.Get(monthPath, a.serveDate)
|
|
||||||
r.Get(monthPath+feedPath, a.serveDate)
|
|
||||||
r.Get(monthPath+paginationPath, a.serveDate)
|
|
||||||
|
|
||||||
dayPath := monthPath + dayRegex
|
|
||||||
r.Get(dayPath, a.serveDate)
|
|
||||||
r.Get(dayPath+feedPath, a.serveDate)
|
|
||||||
r.Get(dayPath+paginationPath, a.serveDate)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Blog
|
|
||||||
if !blogConfig.PostAsHome {
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(sbm)
|
|
||||||
r.With(a.checkActivityStreamsRequest, a.cacheMiddleware).Get(blogConfig.getRelativePath(""), a.serveHome)
|
|
||||||
r.With(a.cacheMiddleware).Get(blogConfig.getRelativePath("")+feedPath, a.serveHome)
|
|
||||||
r.With(a.cacheMiddleware).Get(blogConfig.getRelativePath(paginationPath), a.serveHome)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom pages
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(sbm)
|
|
||||||
for _, cp := range blogConfig.CustomPages {
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
scp := middleware.WithValue(customPageContextKey, cp)
|
|
||||||
if cp.Cache {
|
|
||||||
ce := cp.CacheExpiration
|
|
||||||
if ce == 0 {
|
|
||||||
ce = a.defaultCacheExpiration()
|
|
||||||
}
|
|
||||||
r.With(
|
|
||||||
a.cacheMiddleware,
|
|
||||||
middleware.WithValue(cacheExpirationKey, ce),
|
|
||||||
scp,
|
|
||||||
).Get(cp.Path, a.serveCustomPage)
|
|
||||||
} else {
|
|
||||||
r.With(scp).Get(cp.Path, a.serveCustomPage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Random post
|
|
||||||
if rp := blogConfig.RandomPost; rp != nil && rp.Enabled {
|
|
||||||
r.With(privateModeHandler...).With(sbm).Get(blogConfig.getRelativePath(defaultIfEmpty(rp.Path, "/random")), a.redirectToRandomPost)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editor
|
|
||||||
r.Route(blogConfig.getRelativePath("/editor"), func(r chi.Router) {
|
|
||||||
r.Use(sbm, a.authMiddleware)
|
|
||||||
r.Get("/", a.serveEditor)
|
|
||||||
r.Post("/", a.serveEditorPost)
|
|
||||||
r.Get("/files", a.serveEditorFiles)
|
|
||||||
r.Post("/files/view", a.serveEditorFilesView)
|
|
||||||
r.Post("/files/delete", a.serveEditorFilesDelete)
|
|
||||||
r.Get("/drafts", a.serveDrafts)
|
|
||||||
r.Get("/drafts"+feedPath, a.serveDrafts)
|
|
||||||
r.Get("/drafts"+paginationPath, a.serveDrafts)
|
|
||||||
r.Get("/private", a.servePrivate)
|
|
||||||
r.Get("/private"+feedPath, a.servePrivate)
|
|
||||||
r.Get("/private"+paginationPath, a.servePrivate)
|
|
||||||
r.Get("/unlisted", a.serveUnlisted)
|
|
||||||
r.Get("/unlisted"+feedPath, a.serveUnlisted)
|
|
||||||
r.Get("/unlisted"+paginationPath, a.serveUnlisted)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
|
|
||||||
commentsPath := blogConfig.getRelativePath("/comment")
|
|
||||||
r.Route(commentsPath, func(r chi.Router) {
|
|
||||||
r.Use(sbm, middleware.WithValue(pathContextKey, commentsPath))
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.With(a.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
|
|
||||||
r.With(a.captchaMiddleware).Post("/", a.createComment)
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
// Admin
|
|
||||||
r.Use(a.authMiddleware)
|
|
||||||
r.Get("/", a.commentsAdmin)
|
|
||||||
r.Get(paginationPath, a.commentsAdmin)
|
|
||||||
r.Post("/delete", a.commentsAdminDelete)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blogroll
|
|
||||||
if brConfig := blogConfig.Blogroll; brConfig != nil && brConfig.Enabled {
|
|
||||||
brPath := blogConfig.getRelativePath(defaultIfEmpty(brConfig.Path, defaultBlogrollPath))
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(middleware.WithValue(cacheExpirationKey, a.defaultCacheExpiration()))
|
|
||||||
r.Use(a.cacheMiddleware, sbm)
|
|
||||||
r.Get(brPath, a.serveBlogroll)
|
|
||||||
r.Get(brPath+".opml", a.serveBlogrollExport)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Geo map
|
|
||||||
if mc := blogConfig.Map; mc != nil && mc.Enabled {
|
|
||||||
mapPath := blogConfig.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath))
|
|
||||||
r.Route(mapPath, func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Group(func(r chi.Router) {
|
|
||||||
r.With(a.cacheMiddleware, sbm).Get("/", a.serveGeoMap)
|
|
||||||
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveLeaflet(mapPath+"/"))
|
|
||||||
})
|
|
||||||
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles(mapPath+"/tiles"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contact
|
|
||||||
if cc := blogConfig.Contact; cc != nil && cc.Enabled {
|
|
||||||
contactPath := blogConfig.getRelativePath(defaultIfEmpty(cc.Path, defaultContactPath))
|
|
||||||
r.Route(contactPath, func(r chi.Router) {
|
|
||||||
r.Use(privateModeHandler...)
|
|
||||||
r.Use(a.cacheMiddleware, sbm)
|
|
||||||
r.Get("/", a.serveContactForm)
|
|
||||||
r.With(a.captchaMiddleware).Post("/", a.sendContactSubmission)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sitemap
|
// Sitemap
|
||||||
r.With(privateModeHandler...).With(cacheLoggedIn, a.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
|
r.With(a.privateModeHandler, cacheLoggedIn, a.cacheMiddleware).Get(sitemapPath, a.serveSitemap)
|
||||||
|
|
||||||
// Robots.txt - doesn't need cache, because it's too simple
|
// Robots.txt
|
||||||
if !privateMode {
|
r.With(cacheLoggedIn, a.cacheMiddleware).Get(robotsTXTPath, a.serveRobotsTXT)
|
||||||
r.Get("/robots.txt", a.serveRobotsTXT)
|
|
||||||
} else {
|
|
||||||
r.Get("/robots.txt", servePrivateRobotsTXT)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.NotFound(a.servePostsAliasesRedirects(privateModeHandler...))
|
r.NotFound(a.servePostsAliasesRedirects())
|
||||||
|
|
||||||
r.MethodNotAllowed(a.serveNotAllowed)
|
r.MethodNotAllowed(a.serveNotAllowed)
|
||||||
|
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) servePostsAliasesRedirects(pmh ...func(http.Handler) http.Handler) http.HandlerFunc {
|
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||||
// Private mode
|
// Private mode
|
||||||
alicePrivate := alice.New()
|
alicePrivate := alice.New(a.privateModeHandler)
|
||||||
for _, h := range pmh {
|
|
||||||
alicePrivate = alicePrivate.Append(h)
|
|
||||||
}
|
|
||||||
// Return handler func
|
// Return handler func
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Only allow GET requests
|
// Only allow GET requests
|
||||||
|
@ -550,53 +263,3 @@ func (a *goBlog) servePostsAliasesRedirects(pmh ...func(http.Handler) http.Handl
|
||||||
alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
|
alice.New(a.cacheMiddleware, a.checkRegexRedirects).ThenFunc(a.serve404).ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const blogContextKey contextKey = "blog"
|
|
||||||
const pathContextKey contextKey = "httpPath"
|
|
||||||
|
|
||||||
func (a *goBlog) refreshCSPDomains() {
|
|
||||||
var cspBuilder strings.Builder
|
|
||||||
if mp := a.cfg.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
|
|
||||||
if u, err := url.Parse(mp.MediaURL); err == nil {
|
|
||||||
cspBuilder.WriteByte(' ')
|
|
||||||
cspBuilder.WriteString(u.Hostname())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(a.cfg.Server.CSPDomains) > 0 {
|
|
||||||
cspBuilder.WriteByte(' ')
|
|
||||||
cspBuilder.WriteString(strings.Join(a.cfg.Server.CSPDomains, " "))
|
|
||||||
}
|
|
||||||
a.cspDomains = cspBuilder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
const cspHeader = "Content-Security-Policy"
|
|
||||||
|
|
||||||
func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
|
|
||||||
a.refreshCSPDomains()
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
|
|
||||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
||||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
|
||||||
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
|
||||||
w.Header().Set(cspHeader, "default-src 'self'"+a.cspDomains)
|
|
||||||
if a.cfg.Server.Tor && a.torAddress != "" {
|
|
||||||
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
|
|
||||||
}
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func noIndexHeader(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("X-Robots-Tag", "noindex")
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixHTTPHandler(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
r.URL.RawPath = ""
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func noIndexHeader(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("X-Robots-Tag", "noindex")
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixHTTPHandler(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.URL.RawPath = ""
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
|
||||||
|
// Build CSP domains list
|
||||||
|
var cspBuilder strings.Builder
|
||||||
|
if mp := a.cfg.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
|
||||||
|
if u, err := url.Parse(mp.MediaURL); err == nil {
|
||||||
|
cspBuilder.WriteByte(' ')
|
||||||
|
cspBuilder.WriteString(u.Hostname())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(a.cfg.Server.CSPDomains) > 0 {
|
||||||
|
cspBuilder.WriteByte(' ')
|
||||||
|
cspBuilder.WriteString(strings.Join(a.cfg.Server.CSPDomains, " "))
|
||||||
|
}
|
||||||
|
cspDomains := cspBuilder.String()
|
||||||
|
// Return handler
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000;")
|
||||||
|
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
w.Header().Set("X-Xss-Protection", "1; mode=block")
|
||||||
|
w.Header().Set("Content-Security-Policy", "default-src 'self'"+cspDomains)
|
||||||
|
if a.cfg.Server.Tor && a.torAddress != "" {
|
||||||
|
w.Header().Set("Onion-Location", fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI))
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,414 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login
|
||||||
|
func (a *goBlog) loginRouter(r chi.Router) {
|
||||||
|
r.Use(a.authMiddleware)
|
||||||
|
r.Get("/login", serveLogin)
|
||||||
|
r.Get("/logout", a.serveLogout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Micropub
|
||||||
|
func (a *goBlog) micropubRouter(r chi.Router) {
|
||||||
|
r.Use(a.checkIndieAuth)
|
||||||
|
r.Get("/", a.serveMicropubQuery)
|
||||||
|
r.Post("/", a.serveMicropubPost)
|
||||||
|
r.Post(micropubMediaSubPath, a.serveMicropubMedia)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndieAuth
|
||||||
|
func (a *goBlog) indieAuthRouter(r chi.Router) {
|
||||||
|
r.Get("/", a.indieAuthRequest)
|
||||||
|
r.With(a.authMiddleware).Post("/accept", a.indieAuthAccept)
|
||||||
|
r.Post("/", a.indieAuthVerification)
|
||||||
|
r.Get("/token", a.indieAuthToken)
|
||||||
|
r.Post("/token", a.indieAuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActivityPub
|
||||||
|
func (a *goBlog) activityPubRouter(r chi.Router) {
|
||||||
|
if a.isPrivate() {
|
||||||
|
// Private mode, no ActivityPub
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ap := a.cfg.ActivityPub; ap != nil && ap.Enabled {
|
||||||
|
r.Route("/activitypub", func(r chi.Router) {
|
||||||
|
r.Post("/inbox/{blog}", a.apHandleInbox)
|
||||||
|
r.Post("/{blog}/inbox", a.apHandleInbox)
|
||||||
|
})
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(cacheLoggedIn, a.cacheMiddleware)
|
||||||
|
r.Get("/.well-known/webfinger", a.apHandleWebfinger)
|
||||||
|
r.Get("/.well-known/host-meta", handleWellKnownHostMeta)
|
||||||
|
r.Get("/.well-known/nodeinfo", a.serveNodeInfoDiscover)
|
||||||
|
r.Get("/nodeinfo", a.serveNodeInfo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Webmentions
|
||||||
|
func (a *goBlog) webmentionsRouter(r chi.Router) {
|
||||||
|
if wm := a.cfg.Webmention; wm != nil && !wm.DisableReceiving {
|
||||||
|
// Endpoint
|
||||||
|
r.Post("/", a.handleWebmention)
|
||||||
|
// Authenticated routes
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(a.authMiddleware)
|
||||||
|
r.Get("/", a.webmentionAdmin)
|
||||||
|
r.Get(paginationPath, a.webmentionAdmin)
|
||||||
|
r.Post("/delete", a.webmentionAdminDelete)
|
||||||
|
r.Post("/approve", a.webmentionAdminApprove)
|
||||||
|
r.Post("/reverify", a.webmentionAdminReverify)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
func (a *goBlog) notificationsRouter(r chi.Router) {
|
||||||
|
r.Use(a.authMiddleware)
|
||||||
|
r.Get("/", a.notificationsAdmin)
|
||||||
|
r.Get(paginationPath, a.notificationsAdmin)
|
||||||
|
r.Post("/delete", a.notificationsAdminDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assets
|
||||||
|
func (a *goBlog) assetsRouter(r chi.Router) {
|
||||||
|
for _, path := range a.allAssetPaths() {
|
||||||
|
r.Get(path, a.serveAsset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static files
|
||||||
|
func (a *goBlog) staticFilesRouter(r chi.Router) {
|
||||||
|
r.Use(a.privateModeHandler)
|
||||||
|
for _, path := range allStaticPaths() {
|
||||||
|
r.Get(path, a.serveStaticFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog
|
||||||
|
func (a *goBlog) blogRouter(blog string, conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
|
||||||
|
// Set blog
|
||||||
|
r.Use(middleware.WithValue(blogKey, blog))
|
||||||
|
|
||||||
|
// Home
|
||||||
|
r.Group(a.blogHomeRouter(conf))
|
||||||
|
|
||||||
|
// Sections
|
||||||
|
r.Group(a.blogSectionsRouter(conf))
|
||||||
|
|
||||||
|
// Taxonomies
|
||||||
|
r.Group(a.blogTaxonomiesRouter(conf))
|
||||||
|
|
||||||
|
// Dates
|
||||||
|
r.Group(a.blogDatesRouter(conf))
|
||||||
|
|
||||||
|
// Photos
|
||||||
|
r.Group(a.blogPhotosRouter(conf))
|
||||||
|
|
||||||
|
// Search
|
||||||
|
r.Group(a.blogSearchRouter(conf))
|
||||||
|
|
||||||
|
// Custom pages
|
||||||
|
r.Group(a.blogCustomPagesRouter(conf))
|
||||||
|
|
||||||
|
// Random post
|
||||||
|
r.Group(a.blogRandomRouter(conf))
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
r.Route(conf.getRelativePath(editorPath), a.blogEditorRouter(conf))
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
r.Group(a.blogCommentsRouter(conf))
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
r.Group(a.blogStatsRouter(conf))
|
||||||
|
|
||||||
|
// Blogroll
|
||||||
|
r.Group(a.blogBlogrollRouter(conf))
|
||||||
|
|
||||||
|
// Geo map
|
||||||
|
r.Group(a.blogGeoMapRouter(conf))
|
||||||
|
|
||||||
|
// Contact
|
||||||
|
r.Group(a.blogContactRouter(conf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Home
|
||||||
|
func (a *goBlog) blogHomeRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if !conf.PostAsHome {
|
||||||
|
r.Use(a.privateModeHandler)
|
||||||
|
r.With(a.checkActivityStreamsRequest, a.cacheMiddleware).Get(conf.getRelativePath(""), a.serveHome)
|
||||||
|
r.With(a.cacheMiddleware).Get(conf.getRelativePath("")+feedPath, a.serveHome)
|
||||||
|
r.With(a.cacheMiddleware).Get(conf.getRelativePath(paginationPath), a.serveHome)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Sections
|
||||||
|
func (a *goBlog) blogSectionsRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
a.cacheMiddleware,
|
||||||
|
)
|
||||||
|
for _, section := range conf.Sections {
|
||||||
|
if section.Name != "" {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
secPath := conf.getRelativePath(section.Name)
|
||||||
|
r.Use(middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
|
path: secPath,
|
||||||
|
section: section,
|
||||||
|
}))
|
||||||
|
r.Get(secPath, a.serveIndex)
|
||||||
|
r.Get(secPath+feedPath, a.serveIndex)
|
||||||
|
r.Get(secPath+paginationPath, a.serveIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Taxonomies
|
||||||
|
func (a *goBlog) blogTaxonomiesRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
a.cacheMiddleware,
|
||||||
|
)
|
||||||
|
for _, taxonomy := range conf.Taxonomies {
|
||||||
|
if taxonomy.Name != "" {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(middleware.WithValue(taxonomyContextKey, taxonomy))
|
||||||
|
taxBasePath := conf.getRelativePath(taxonomy.Name)
|
||||||
|
r.Get(taxBasePath, a.serveTaxonomy)
|
||||||
|
taxValPath := taxBasePath + "/{taxValue}"
|
||||||
|
r.Get(taxValPath, a.serveTaxonomyValue)
|
||||||
|
r.Get(taxValPath+feedPath, a.serveTaxonomyValue)
|
||||||
|
r.Get(taxValPath+paginationPath, a.serveTaxonomyValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Dates
|
||||||
|
func (a *goBlog) blogDatesRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
a.cacheMiddleware,
|
||||||
|
)
|
||||||
|
|
||||||
|
yearPath := conf.getRelativePath(`/{year:x|\d\d\d\d}`)
|
||||||
|
r.Get(yearPath, a.serveDate)
|
||||||
|
r.Get(yearPath+feedPath, a.serveDate)
|
||||||
|
r.Get(yearPath+paginationPath, a.serveDate)
|
||||||
|
|
||||||
|
monthPath := yearPath + `/{month:x|\d\d}`
|
||||||
|
r.Get(monthPath, a.serveDate)
|
||||||
|
r.Get(monthPath+feedPath, a.serveDate)
|
||||||
|
r.Get(monthPath+paginationPath, a.serveDate)
|
||||||
|
|
||||||
|
dayPath := monthPath + `/{day:\d\d}`
|
||||||
|
r.Get(dayPath, a.serveDate)
|
||||||
|
r.Get(dayPath+feedPath, a.serveDate)
|
||||||
|
r.Get(dayPath+paginationPath, a.serveDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Photos
|
||||||
|
func (a *goBlog) blogPhotosRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if pc := conf.Photos; pc != nil && pc.Enabled {
|
||||||
|
photoPath := conf.getRelativePath(defaultIfEmpty(pc.Path, defaultPhotosPath))
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
a.cacheMiddleware,
|
||||||
|
middleware.WithValue(indexConfigKey, &indexConfig{
|
||||||
|
path: photoPath,
|
||||||
|
parameter: pc.Parameter,
|
||||||
|
title: pc.Title,
|
||||||
|
description: pc.Description,
|
||||||
|
summaryTemplate: templatePhotosSummary,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
r.Get(photoPath, a.serveIndex)
|
||||||
|
r.Get(photoPath+feedPath, a.serveIndex)
|
||||||
|
r.Get(photoPath+paginationPath, a.serveIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Search
|
||||||
|
func (a *goBlog) blogSearchRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if bsc := conf.Search; bsc != nil && bsc.Enabled {
|
||||||
|
searchPath := conf.getRelativePath(defaultIfEmpty(bsc.Path, defaultSearchPath))
|
||||||
|
r.Route(searchPath, func(r chi.Router) {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
a.cacheMiddleware,
|
||||||
|
middleware.WithValue(pathKey, searchPath),
|
||||||
|
)
|
||||||
|
r.Get("/", a.serveSearch)
|
||||||
|
r.Post("/", a.serveSearch)
|
||||||
|
searchResultPath := "/" + searchPlaceholder
|
||||||
|
r.Get(searchResultPath, a.serveSearchResult)
|
||||||
|
r.Get(searchResultPath+feedPath, a.serveSearchResult)
|
||||||
|
r.Get(searchResultPath+paginationPath, a.serveSearchResult)
|
||||||
|
})
|
||||||
|
r.With(
|
||||||
|
// No private mode, to allow using OpenSearch in browser
|
||||||
|
a.cacheMiddleware,
|
||||||
|
middleware.WithValue(pathKey, searchPath),
|
||||||
|
).Get("/opensearch.xml", a.serveOpenSearch)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Custom pages
|
||||||
|
func (a *goBlog) blogCustomPagesRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
r.Use(a.privateModeHandler)
|
||||||
|
for _, cp := range conf.CustomPages {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Use(middleware.WithValue(customPageContextKey, cp))
|
||||||
|
if cp.Cache {
|
||||||
|
ce := cp.CacheExpiration
|
||||||
|
if ce == 0 {
|
||||||
|
ce = a.defaultCacheExpiration()
|
||||||
|
}
|
||||||
|
r.Use(
|
||||||
|
a.cacheMiddleware,
|
||||||
|
middleware.WithValue(cacheExpirationKey, ce),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
r.Get(cp.Path, a.serveCustomPage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Random
|
||||||
|
func (a *goBlog) blogRandomRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if rp := conf.RandomPost; rp != nil && rp.Enabled {
|
||||||
|
r.With(a.privateModeHandler).Get(conf.getRelativePath(defaultIfEmpty(rp.Path, "/random")), a.redirectToRandomPost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Editor
|
||||||
|
func (a *goBlog) blogEditorRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
r.Use(a.authMiddleware)
|
||||||
|
r.Get("/", a.serveEditor)
|
||||||
|
r.Post("/", a.serveEditorPost)
|
||||||
|
r.Get("/files", a.serveEditorFiles)
|
||||||
|
r.Post("/files/view", a.serveEditorFilesView)
|
||||||
|
r.Post("/files/delete", a.serveEditorFilesDelete)
|
||||||
|
r.Get("/drafts", a.serveDrafts)
|
||||||
|
r.Get("/drafts"+feedPath, a.serveDrafts)
|
||||||
|
r.Get("/drafts"+paginationPath, a.serveDrafts)
|
||||||
|
r.Get("/private", a.servePrivate)
|
||||||
|
r.Get("/private"+feedPath, a.servePrivate)
|
||||||
|
r.Get("/private"+paginationPath, a.servePrivate)
|
||||||
|
r.Get("/unlisted", a.serveUnlisted)
|
||||||
|
r.Get("/unlisted"+feedPath, a.serveUnlisted)
|
||||||
|
r.Get("/unlisted"+paginationPath, a.serveUnlisted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Comments
|
||||||
|
func (a *goBlog) blogCommentsRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if commentsConfig := conf.Comments; commentsConfig != nil && commentsConfig.Enabled {
|
||||||
|
commentsPath := conf.getRelativePath("/comment")
|
||||||
|
r.Route(commentsPath, func(r chi.Router) {
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
middleware.WithValue(pathKey, commentsPath),
|
||||||
|
)
|
||||||
|
r.With(a.cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", a.serveComment)
|
||||||
|
r.With(a.captchaMiddleware).Post("/", a.createComment)
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
// Admin
|
||||||
|
r.Use(a.authMiddleware)
|
||||||
|
r.Get("/", a.commentsAdmin)
|
||||||
|
r.Get(paginationPath, a.commentsAdmin)
|
||||||
|
r.Post("/delete", a.commentsAdminDelete)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Stats
|
||||||
|
func (a *goBlog) blogStatsRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if bsc := conf.BlogStats; bsc != nil && bsc.Enabled {
|
||||||
|
statsPath := conf.getRelativePath(defaultIfEmpty(bsc.Path, defaultBlogStatsPath))
|
||||||
|
r.Use(a.privateModeHandler)
|
||||||
|
r.With(a.cacheMiddleware).Get(statsPath, a.serveBlogStats)
|
||||||
|
r.With(cacheLoggedIn, a.cacheMiddleware).Get(statsPath+blogStatsTablePath, a.serveBlogStatsTable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Blogroll
|
||||||
|
func (a *goBlog) blogBlogrollRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if brConfig := conf.Blogroll; brConfig != nil && brConfig.Enabled {
|
||||||
|
brPath := conf.getRelativePath(defaultIfEmpty(brConfig.Path, defaultBlogrollPath))
|
||||||
|
r.Use(
|
||||||
|
a.privateModeHandler,
|
||||||
|
middleware.WithValue(cacheExpirationKey, a.defaultCacheExpiration()),
|
||||||
|
a.cacheMiddleware,
|
||||||
|
)
|
||||||
|
r.Get(brPath, a.serveBlogroll)
|
||||||
|
r.Get(brPath+".opml", a.serveBlogrollExport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Geo Map
|
||||||
|
func (a *goBlog) blogGeoMapRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if mc := conf.Map; mc != nil && mc.Enabled {
|
||||||
|
mapPath := conf.getRelativePath(defaultIfEmpty(mc.Path, defaultGeoMapPath))
|
||||||
|
r.Route(mapPath, func(r chi.Router) {
|
||||||
|
r.Use(a.privateModeHandler)
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.With(a.cacheMiddleware).Get("/", a.serveGeoMap)
|
||||||
|
r.With(cacheLoggedIn, a.cacheMiddleware).HandleFunc("/leaflet/*", a.serveLeaflet(mapPath+"/"))
|
||||||
|
})
|
||||||
|
r.Get("/tiles/{z}/{x}/{y}.png", a.proxyTiles(mapPath+"/tiles"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blog - Contact
|
||||||
|
func (a *goBlog) blogContactRouter(conf *configBlog) func(r chi.Router) {
|
||||||
|
return func(r chi.Router) {
|
||||||
|
if cc := conf.Contact; cc != nil && cc.Enabled {
|
||||||
|
contactPath := conf.getRelativePath(defaultIfEmpty(cc.Path, defaultContactPath))
|
||||||
|
r.Route(contactPath, func(r chi.Router) {
|
||||||
|
r.Use(a.privateModeHandler, a.cacheMiddleware)
|
||||||
|
r.Get("/", a.serveContactForm)
|
||||||
|
r.With(a.captchaMiddleware).Post("/", a.sendContactSubmission)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,7 +57,7 @@ func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Try to compress file (only when not in private mode)
|
// Try to compress file (only when not in private mode)
|
||||||
if pm := a.cfg.PrivateMode; pm == nil || !pm.Enabled {
|
if !a.isPrivate() {
|
||||||
compressedLocation, compressionErr := a.compressMediaFile(location)
|
compressedLocation, compressionErr := a.compressMediaFile(location)
|
||||||
if compressionErr != nil {
|
if compressionErr != nil {
|
||||||
a.serveError(w, r, "failed to compress file: "+compressionErr.Error(), http.StatusInternalServerError)
|
a.serveError(w, r, "failed to compress file: "+compressionErr.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
b := a.cfg.Blogs[blog]
|
b := a.cfg.Blogs[blog]
|
||||||
title := b.Title
|
title := b.Title
|
||||||
sURL := a.getFullAddress(b.getRelativePath(defaultIfEmpty(b.Search.Path, defaultSearchPath)))
|
sURL := a.getFullAddress(b.getRelativePath(defaultIfEmpty(b.Search.Path, defaultSearchPath)))
|
||||||
|
|
14
posts.go
14
posts.go
|
@ -78,7 +78,7 @@ func (a *goBlog) servePost(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) redirectToRandomPost(rw http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) redirectToRandomPost(rw http.ResponseWriter, r *http.Request) {
|
||||||
randomPath, err := a.getRandomPostPath(r.Context().Value(blogContextKey).(string))
|
randomPath, err := a.getRandomPostPath(r.Context().Value(blogKey).(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(rw, r, err.Error(), http.StatusInternalServerError)
|
a.serveError(rw, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -111,7 +111,7 @@ func (p *postPaginationAdapter) Slice(offset, length int, data interface{}) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveHome(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
|
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
|
||||||
a.serveActivityStreams(blog, w, r)
|
a.serveActivityStreams(blog, w, r)
|
||||||
return
|
return
|
||||||
|
@ -122,7 +122,7 @@ func (a *goBlog) serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveDrafts(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveDrafts(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
path: a.getRelativePath(blog, "/editor/drafts"),
|
path: a.getRelativePath(blog, "/editor/drafts"),
|
||||||
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "drafts"),
|
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "drafts"),
|
||||||
|
@ -131,7 +131,7 @@ func (a *goBlog) serveDrafts(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) servePrivate(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) servePrivate(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
path: a.getRelativePath(blog, "/editor/private"),
|
path: a.getRelativePath(blog, "/editor/private"),
|
||||||
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "privateposts"),
|
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "privateposts"),
|
||||||
|
@ -140,7 +140,7 @@ func (a *goBlog) servePrivate(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveUnlisted(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveUnlisted(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
path: a.getRelativePath(blog, "/editor/unlisted"),
|
path: a.getRelativePath(blog, "/editor/unlisted"),
|
||||||
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "unlistedposts"),
|
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "unlistedposts"),
|
||||||
|
@ -184,7 +184,7 @@ func (a *goBlog) serveDate(w http.ResponseWriter, r *http.Request) {
|
||||||
dPath.WriteString(fmt.Sprintf("/%02d", day))
|
dPath.WriteString(fmt.Sprintf("/%02d", day))
|
||||||
}
|
}
|
||||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
path: a.getRelativePath(r.Context().Value(blogContextKey).(string), dPath.String()),
|
path: a.getRelativePath(r.Context().Value(blogKey).(string), dPath.String()),
|
||||||
year: year,
|
year: year,
|
||||||
month: month,
|
month: month,
|
||||||
day: day,
|
day: day,
|
||||||
|
@ -214,7 +214,7 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
ic := r.Context().Value(indexConfigKey).(*indexConfig)
|
ic := r.Context().Value(indexConfigKey).(*indexConfig)
|
||||||
blog := ic.blog
|
blog := ic.blog
|
||||||
if blog == "" {
|
if blog == "" {
|
||||||
blog, _ = r.Context().Value(blogContextKey).(string)
|
blog, _ = r.Context().Value(blogKey).(string)
|
||||||
}
|
}
|
||||||
search := chi.URLParam(r, "search")
|
search := chi.URLParam(r, "search")
|
||||||
if search != "" {
|
if search != "" {
|
||||||
|
|
|
@ -79,7 +79,7 @@ func (a *goBlog) checkPost(p *post) (err error) {
|
||||||
random := generateRandomString(5)
|
random := generateRandomString(5)
|
||||||
p.Slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
p.Slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random)
|
||||||
}
|
}
|
||||||
published, _ := dateparse.ParseLocal(p.Published)
|
published := timeNoErr(dateparse.ParseLocal(p.Published))
|
||||||
pathTmplString := a.cfg.Blogs[p.Blog].Sections[p.Section].PathTemplate
|
pathTmplString := a.cfg.Blogs[p.Blog].Sections[p.Section].PathTemplate
|
||||||
if pathTmplString == "" {
|
if pathTmplString == "" {
|
||||||
return errors.New("path template empty")
|
return errors.New("path template empty")
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/justinas/alice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *goBlog) isPrivate() bool {
|
||||||
|
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *goBlog) privateModeHandler(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if a.isPrivate() {
|
||||||
|
alice.New(a.authMiddleware).Then(next).ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
10
robotstxt.go
10
robotstxt.go
|
@ -5,10 +5,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const robotsTXTPath = "/robots.txt"
|
||||||
|
|
||||||
func (a *goBlog) serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveRobotsTXT(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if a.isPrivate() {
|
||||||
|
_, _ = w.Write([]byte("User-agent: *\nDisallow: /"))
|
||||||
|
return
|
||||||
|
}
|
||||||
_, _ = w.Write([]byte(fmt.Sprintf("User-agent: *\nSitemap: %v", a.getFullAddress(sitemapPath))))
|
_, _ = w.Write([]byte(fmt.Sprintf("User-agent: *\nSitemap: %v", a.getFullAddress(sitemapPath))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func servePrivateRobotsTXT(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_, _ = w.Write([]byte("User-agent: *\nDisallow: /"))
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,11 +9,6 @@ import (
|
||||||
|
|
||||||
func Test_robotsTXT(t *testing.T) {
|
func Test_robotsTXT(t *testing.T) {
|
||||||
|
|
||||||
h := http.HandlerFunc(servePrivateRobotsTXT)
|
|
||||||
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
|
||||||
txt := assert.HTTPBody(h, http.MethodGet, "", nil)
|
|
||||||
assert.Equal(t, "User-agent: *\nDisallow: /", txt)
|
|
||||||
|
|
||||||
app := &goBlog{
|
app := &goBlog{
|
||||||
cfg: &config{
|
cfg: &config{
|
||||||
Server: &configServer{
|
Server: &configServer{
|
||||||
|
@ -22,9 +17,18 @@ func Test_robotsTXT(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h := http.HandlerFunc(app.serveRobotsTXT)
|
||||||
|
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
||||||
|
txt := assert.HTTPBody(h, http.MethodGet, "", nil)
|
||||||
|
assert.Equal(t, "User-agent: *\nSitemap: https://example.com/sitemap.xml", txt)
|
||||||
|
|
||||||
|
app.cfg.PrivateMode = &configPrivateMode{
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
h = http.HandlerFunc(app.serveRobotsTXT)
|
h = http.HandlerFunc(app.serveRobotsTXT)
|
||||||
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
assert.HTTPStatusCode(t, h, http.MethodGet, "", nil, 200)
|
||||||
txt = assert.HTTPBody(h, http.MethodGet, "", nil)
|
txt = assert.HTTPBody(h, http.MethodGet, "", nil)
|
||||||
assert.Equal(t, "User-agent: *\nSitemap: https://example.com/sitemap.xml", txt)
|
assert.Equal(t, "User-agent: *\nDisallow: /", txt)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ const defaultSearchPath = "/search"
|
||||||
const searchPlaceholder = "{search}"
|
const searchPlaceholder = "{search}"
|
||||||
|
|
||||||
func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
servePath := r.Context().Value(pathContextKey).(string)
|
servePath := r.Context().Value(pathKey).(string)
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
@ -37,7 +37,7 @@ func (a *goBlog) serveSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (a *goBlog) serveSearchResult(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveSearchResult(w http.ResponseWriter, r *http.Request) {
|
||||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||||
path: r.Context().Value(pathContextKey).(string) + "/" + searchPlaceholder,
|
path: r.Context().Value(pathKey).(string) + "/" + searchPlaceholder,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,15 +114,12 @@ func (s *dbSessionStore) load(session *sessions.Session) (err error) {
|
||||||
if err = row.Scan(&data, &createdStr, &modifiedStr, &expiresStr); err != nil {
|
if err = row.Scan(&data, &createdStr, &modifiedStr, &expiresStr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
created, _ := dateparse.ParseLocal(createdStr)
|
|
||||||
modified, _ := dateparse.ParseLocal(modifiedStr)
|
|
||||||
expires, _ := dateparse.ParseLocal(expiresStr)
|
|
||||||
if err = securecookie.DecodeMulti(session.Name(), data, &session.Values, s.codecs...); err != nil {
|
if err = securecookie.DecodeMulti(session.Name(), data, &session.Values, s.codecs...); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
session.Values[sessionCreatedOn] = created
|
session.Values[sessionCreatedOn] = timeNoErr(dateparse.ParseLocal(createdStr))
|
||||||
session.Values[sessionModifiedOn] = modified
|
session.Values[sessionModifiedOn] = timeNoErr(dateparse.ParseLocal(modifiedStr))
|
||||||
session.Values[sessionExpiresOn] = expires
|
session.Values[sessionExpiresOn] = timeNoErr(dateparse.ParseLocal(expiresStr))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,16 +143,16 @@ func (a *goBlog) serveSitemap(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Posts
|
// Published posts
|
||||||
if posts, err := a.db.getPosts(&postsRequestConfig{status: statusPublished, withoutParameters: true}); err == nil {
|
if posts, err := a.db.getPosts(&postsRequestConfig{status: statusPublished, withoutParameters: true}); err == nil {
|
||||||
for _, p := range posts {
|
for _, p := range posts {
|
||||||
item := &sitemap.URL{Loc: a.fullPostURL(p)}
|
item := &sitemap.URL{Loc: a.fullPostURL(p)}
|
||||||
var lastMod time.Time
|
var lastMod time.Time
|
||||||
if p.Updated != "" {
|
if p.Updated != "" {
|
||||||
lastMod, _ = dateparse.ParseLocal(p.Updated)
|
lastMod = timeNoErr(dateparse.ParseLocal(p.Updated))
|
||||||
}
|
}
|
||||||
if p.Published != "" && lastMod.IsZero() {
|
if p.Published != "" && lastMod.IsZero() {
|
||||||
lastMod, _ = dateparse.ParseLocal(p.Published)
|
lastMod = timeNoErr(dateparse.ParseLocal(p.Published))
|
||||||
}
|
}
|
||||||
if !lastMod.IsZero() {
|
if !lastMod.IsZero() {
|
||||||
item.LastMod = &lastMod
|
item.LastMod = &lastMod
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
const taxonomyContextKey = "taxonomy"
|
const taxonomyContextKey = "taxonomy"
|
||||||
|
|
||||||
func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
tax := r.Context().Value(taxonomyContextKey).(*configTaxonomy)
|
tax := r.Context().Value(taxonomyContextKey).(*configTaxonomy)
|
||||||
allValues, err := a.db.allTaxonomyValues(blog, tax.Name)
|
allValues, err := a.db.allTaxonomyValues(blog, tax.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,7 +31,7 @@ func (a *goBlog) serveTaxonomy(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *goBlog) serveTaxonomyValue(w http.ResponseWriter, r *http.Request) {
|
func (a *goBlog) serveTaxonomyValue(w http.ResponseWriter, r *http.Request) {
|
||||||
blog := r.Context().Value(blogContextKey).(string)
|
blog := r.Context().Value(blogKey).(string)
|
||||||
tax := r.Context().Value(taxonomyContextKey).(*configTaxonomy)
|
tax := r.Context().Value(taxonomyContextKey).(*configTaxonomy)
|
||||||
taxValueParam := chi.URLParam(r, "taxValue")
|
taxValueParam := chi.URLParam(r, "taxValue")
|
||||||
if taxValueParam == "" {
|
if taxValueParam == "" {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{{ $blog := .Blog }}
|
{{ $blog := .Blog }}
|
||||||
{{ range $i, $tax := $blog.Taxonomies }}
|
{{ range $i, $tax := $blog.Taxonomies }}
|
||||||
{{ $tvs := sort (ps $post $tax.Name) }}
|
{{ $tvs := sort (ps $post $tax.Name) }}
|
||||||
{{ if gt (len $tvs) 0 }}
|
{{ if $tvs }}
|
||||||
<p><b>{{ $tax.Title }}</b>:
|
<p><b>{{ $tax.Title }}</b>:
|
||||||
{{ range $j, $tv := $tvs }}
|
{{ range $j, $tv := $tvs }}
|
||||||
<a class="p-category" rel="tag" href="{{ $blog.RelativePath ( printf "/%s/%s" $tax.Name (urlize $tv) ) }}">{{ $tv }}</a>
|
<a class="p-category" rel="tag" href="{{ $blog.RelativePath ( printf "/%s/%s" $tax.Name (urlize $tv) ) }}">{{ $tv }}</a>
|
||||||
|
|
4
utils.go
4
utils.go
|
@ -256,3 +256,7 @@ func containsStrings(s string, subStrings ...string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func timeNoErr(t time.Time, _ error) time.Time {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// External mention
|
// External mention
|
||||||
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
if a.isPrivate() {
|
||||||
// Private mode, don't send external mentions
|
// Private mode, don't send external mentions
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue