mirror of https://github.com/jlelse/GoBlog
Reduce complexity of router build method
parent
91e2b268c7
commit
88f26ef3c6
|
@ -22,7 +22,12 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
}
|
||||
// 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) {
|
||||
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
|
||||
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)))
|
||||
|
|
2
app.go
2
app.go
|
@ -45,8 +45,6 @@ type goBlog struct {
|
|||
pUpdateHooks []postHookFunc
|
||||
pDeleteHooks []postHookFunc
|
||||
hourlyHooks []hourlyHookFunc
|
||||
// HTTP
|
||||
cspDomains string
|
||||
// HTTP Client
|
||||
httpClient httpClient
|
||||
// HTTP Routers
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
const defaultBlogrollPath = "/blogroll"
|
||||
|
||||
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) {
|
||||
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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
outlines, err, _ := a.blogrollCacheGroup.Do(blog, func() (interface{}, error) {
|
||||
return a.getBlogrollOutlines(blog)
|
||||
})
|
||||
|
|
11
blogstats.go
11
blogstats.go
|
@ -7,7 +7,10 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
const defaultBlogStatsPath = "/statistics"
|
||||
const (
|
||||
defaultBlogStatsPath = "/statistics"
|
||||
blogStatsTablePath = ".table.html"
|
||||
)
|
||||
|
||||
func (a *goBlog) initBlogStats() {
|
||||
f := func(p *post) {
|
||||
|
@ -19,20 +22,20 @@ func (a *goBlog) initBlogStats() {
|
|||
}
|
||||
|
||||
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]
|
||||
canonical := bc.getRelativePath(defaultIfEmpty(bc.BlogStats.Path, defaultBlogStatsPath))
|
||||
a.render(w, r, templateBlogStats, &renderData{
|
||||
BlogString: blog,
|
||||
Canonical: a.getFullAddress(canonical),
|
||||
Data: map[string]interface{}{
|
||||
"TableUrl": canonical + ".table.html",
|
||||
"TableUrl": canonical + blogStatsTablePath,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
return
|
||||
}
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
a.render(w, r, templateComment, &renderData{
|
||||
BlogString: blog,
|
||||
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
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
} 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
|
||||
_ = a.createWebmention(a.getFullAddress(commentAddress), a.getFullAddress(target))
|
||||
// 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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
commentsPath := r.Context().Value(pathContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
commentsPath := r.Context().Value(pathKey).(string)
|
||||
// Adapter
|
||||
pageNoString := chi.URLParam(r, "page")
|
||||
pageNo, _ := strconv.Atoi(pageNoString)
|
||||
|
|
|
@ -290,8 +290,6 @@ func (a *goBlog) initConfig() error {
|
|||
viper.SetDefault("micropub.locationParam", "location")
|
||||
viper.SetDefault("activityPub.keyPath", "data/private.pem")
|
||||
viper.SetDefault("activityPub.tagsTaxonomies", []string{"tags"})
|
||||
viper.SetDefault("webmention.disableSending", false)
|
||||
viper.SetDefault("webmention.disableReceiving", false)
|
||||
// Unmarshal config
|
||||
a.cfg = &config{}
|
||||
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, "/")
|
||||
}
|
||||
if pm := a.cfg.PrivateMode; pm != nil && pm.Enabled {
|
||||
a.cfg.ActivityPub = &configActivityPub{Enabled: false}
|
||||
}
|
||||
if wm := a.cfg.Webmention; wm != nil && wm.DisableReceiving {
|
||||
// Disable comments for all blogs
|
||||
for _, b := range a.cfg.Blogs {
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
const defaultContactPath = "/contact"
|
||||
|
||||
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
|
||||
a.render(w, r, templateContact, &renderData{
|
||||
BlogString: blog,
|
||||
|
@ -62,7 +62,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
_, _ = message.WriteString(formMessage)
|
||||
// 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 != "" {
|
||||
// Build email
|
||||
var email bytes.Buffer
|
||||
|
|
|
@ -7,7 +7,7 @@ const customPageContextKey = "custompage"
|
|||
func (a *goBlog) serveCustomPage(w http.ResponseWriter, r *http.Request) {
|
||||
page := r.Context().Value(customPageContextKey).(*configCustomPage)
|
||||
a.render(w, r, page.Template, &renderData{
|
||||
BlogString: r.Context().Value(blogContextKey).(string),
|
||||
BlogString: r.Context().Value(blogKey).(string),
|
||||
Canonical: a.getFullAddress(page.Path),
|
||||
Data: page.Data,
|
||||
})
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
const editorPath = "/editor"
|
||||
|
||||
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{
|
||||
BlogString: blog,
|
||||
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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
if action := r.FormValue("editoraction"); action != "" {
|
||||
switch action {
|
||||
case "loaddelete":
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
// Get files
|
||||
files, err := a.mediaFiles()
|
||||
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)
|
||||
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 {
|
||||
created, _ := dateparse.ParseLocal(p.Published)
|
||||
updated, _ := dateparse.ParseLocal(p.Updated)
|
||||
feed.Add(&feeds.Item{
|
||||
Title: p.Title(),
|
||||
Link: &feeds.Link{Href: a.fullPostURL(p)},
|
||||
Description: a.postSummary(p),
|
||||
Id: p.Path,
|
||||
Content: string(a.postHtml(p, true)),
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
Created: timeNoErr(dateparse.ParseLocal(p.Published)),
|
||||
Updated: timeNoErr(dateparse.ParseLocal(p.Updated)),
|
||||
})
|
||||
}
|
||||
var err error
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
const defaultGeoMapPath = "/map"
|
||||
|
||||
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]
|
||||
|
||||
allPostsWithLocation, err := a.db.getPosts(&postsRequestConfig{
|
||||
|
|
2
go.mod
2
go.mod
|
@ -53,7 +53,7 @@ require (
|
|||
// master
|
||||
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
|
||||
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/sys v0.0.0-20210630005230-0f9fa26af87c // 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-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-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
|
||||
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
|
||||
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-20190226205417-e64efc72b421/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"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
|
@ -26,16 +24,18 @@ const (
|
|||
contentType = "Content-Type"
|
||||
userAgent = "User-Agent"
|
||||
appUserAgent = "GoBlog"
|
||||
|
||||
blogKey contextKey = "blog"
|
||||
pathKey contextKey = "httpPath"
|
||||
)
|
||||
|
||||
func (a *goBlog) startServer() (err error) {
|
||||
log.Println("Start server(s)...")
|
||||
// Load router
|
||||
router, err := a.buildRouter()
|
||||
a.d, err = a.buildRouter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.d = fixHTTPHandler(router)
|
||||
// Set basic middlewares
|
||||
h := alice.New()
|
||||
if a.cfg.Server.Logging {
|
||||
|
@ -126,28 +126,21 @@ const (
|
|||
feedPath = ".{feed:rss|json|atom}"
|
||||
)
|
||||
|
||||
func (a *goBlog) buildRouter() (*chi.Mux, error) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// 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)
|
||||
}
|
||||
func (a *goBlog) buildRouter() (http.Handler, error) {
|
||||
r := chi.NewMux()
|
||||
|
||||
// Basic middleware
|
||||
r.Use(fixHTTPHandler)
|
||||
r.Use(a.redirectShortDomain)
|
||||
r.Use(middleware.RedirectSlashes)
|
||||
r.Use(middleware.CleanPath)
|
||||
r.Use(middleware.GetHead)
|
||||
if !a.cfg.Cache.Enable {
|
||||
if cache := a.cfg.Cache; cache != nil && !cache.Enable {
|
||||
r.Use(middleware.NoCache)
|
||||
}
|
||||
|
||||
// No Index Header
|
||||
if privateMode {
|
||||
if a.isPrivate() {
|
||||
r.Use(noIndexHeader)
|
||||
}
|
||||
|
||||
|
@ -155,340 +148,60 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
|
|||
r.Use(a.checkIsLogin)
|
||||
r.Use(a.checkIsCaptcha)
|
||||
|
||||
// Logout
|
||||
r.With(a.authMiddleware).Get("/login", serveLogin)
|
||||
r.With(a.authMiddleware).Get("/logout", a.serveLogout)
|
||||
// Login
|
||||
r.Group(a.loginRouter)
|
||||
|
||||
// Micropub
|
||||
r.Route(micropubPath, func(r chi.Router) {
|
||||
r.Use(a.checkIndieAuth)
|
||||
r.Get("/", a.serveMicropubQuery)
|
||||
r.Post("/", a.serveMicropubPost)
|
||||
r.Post(micropubMediaSubPath, a.serveMicropubMedia)
|
||||
})
|
||||
r.Route(micropubPath, a.micropubRouter)
|
||||
|
||||
// IndieAuth
|
||||
r.Route("/indieauth", func(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)
|
||||
})
|
||||
r.Route("/indieauth", a.indieAuthRouter)
|
||||
|
||||
// ActivityPub and stuff
|
||||
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)
|
||||
})
|
||||
}
|
||||
r.Group(a.activityPubRouter)
|
||||
|
||||
// Webmentions
|
||||
if wm := a.cfg.Webmention; wm != nil && !wm.DisableReceiving {
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
r.Route(webmentionPath, a.webmentionsRouter)
|
||||
|
||||
// Notifications
|
||||
r.Route(notificationsPath, func(r chi.Router) {
|
||||
r.Use(a.authMiddleware)
|
||||
r.Get("/", a.notificationsAdmin)
|
||||
r.Get(paginationPath, a.notificationsAdmin)
|
||||
r.Post("/delete", a.notificationsAdminDelete)
|
||||
})
|
||||
r.Route(notificationsPath, a.notificationsRouter)
|
||||
|
||||
// Assets
|
||||
for _, path := range a.allAssetPaths() {
|
||||
r.Get(path, a.serveAsset)
|
||||
}
|
||||
r.Group(a.assetsRouter)
|
||||
|
||||
// Static files
|
||||
for _, path := range allStaticPaths() {
|
||||
r.With(privateModeHandler...).Get(path, a.serveStaticFile)
|
||||
}
|
||||
r.Group(a.staticFilesRouter)
|
||||
|
||||
// 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
|
||||
r.Handle("/captcha/*", captcha.Server(500, 250))
|
||||
|
||||
// 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 {
|
||||
sbm := middleware.WithValue(blogContextKey, blog)
|
||||
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
r.Group(a.blogRouter(blog, blogConfig))
|
||||
}
|
||||
|
||||
// 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
|
||||
if !privateMode {
|
||||
r.Get("/robots.txt", a.serveRobotsTXT)
|
||||
} else {
|
||||
r.Get("/robots.txt", servePrivateRobotsTXT)
|
||||
}
|
||||
// Robots.txt
|
||||
r.With(cacheLoggedIn, a.cacheMiddleware).Get(robotsTXTPath, a.serveRobotsTXT)
|
||||
|
||||
r.NotFound(a.servePostsAliasesRedirects(privateModeHandler...))
|
||||
r.NotFound(a.servePostsAliasesRedirects())
|
||||
|
||||
r.MethodNotAllowed(a.serveNotAllowed)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (a *goBlog) servePostsAliasesRedirects(pmh ...func(http.Handler) http.Handler) http.HandlerFunc {
|
||||
func (a *goBlog) servePostsAliasesRedirects() http.HandlerFunc {
|
||||
// Private mode
|
||||
alicePrivate := alice.New()
|
||||
for _, h := range pmh {
|
||||
alicePrivate = alicePrivate.Append(h)
|
||||
}
|
||||
alicePrivate := alice.New(a.privateModeHandler)
|
||||
// Return handler func
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// 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)
|
||||
if compressionErr != nil {
|
||||
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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
b := a.cfg.Blogs[blog]
|
||||
title := b.Title
|
||||
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) {
|
||||
randomPath, err := a.getRandomPostPath(r.Context().Value(blogContextKey).(string))
|
||||
randomPath, err := a.getRandomPostPath(r.Context().Value(blogKey).(string))
|
||||
if err != nil {
|
||||
a.serveError(rw, r, err.Error(), http.StatusInternalServerError)
|
||||
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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
if asRequest, ok := r.Context().Value(asRequestKey).(bool); ok && asRequest {
|
||||
a.serveActivityStreams(blog, w, r)
|
||||
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) {
|
||||
blog := r.Context().Value(blogContextKey).(string)
|
||||
blog := r.Context().Value(blogKey).(string)
|
||||
a.serveIndex(w, r.WithContext(context.WithValue(r.Context(), indexConfigKey, &indexConfig{
|
||||
path: a.getRelativePath(blog, "/editor/drafts"),
|
||||
title: a.ts.GetTemplateStringVariant(a.cfg.Blogs[blog].Lang, "drafts"),
|
||||
|