Various improvements; Remove Hugo import API; Add feeds for photos

This commit is contained in:
Jan-Lukas Else 2021-02-16 16:26:21 +01:00
parent 82ace66544
commit 99789efcd3
19 changed files with 212 additions and 337 deletions

View File

@ -84,8 +84,7 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
serveError(w, r, "Blog not found", http.StatusNotFound)
return
}
w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
b, _ := json.Marshal(map[string]interface{}{
"subject": "acct:" + name + "@" + appConfig.Server.publicHostname,
"links": []map[string]string{
{
@ -95,6 +94,8 @@ func apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
},
},
})
w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix)
_, _ = writeMinified(w, contentTypeJSON, b)
}
func apHandleInbox(w http.ResponseWriter, r *http.Request) {

View File

@ -14,10 +14,12 @@ var asContext = []string{"https://www.w3.org/ns/activitystreams"}
func manipulateAsPath(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, contentTypeAS) || strings.Contains(lowerAccept, "application/ld+json")) && !strings.Contains(lowerAccept, contentTypeHTML) {
// Is ActivityStream, add ".as" to differentiate cache and also trigger as function
r.URL.Path += ".as"
}
}
next.ServeHTTP(rw, r)
})
}
@ -68,9 +70,9 @@ type asEndpoints struct {
}
func (p *post) serveActivityStreams(w http.ResponseWriter) {
// Send JSON
w.Header().Add(contentType, contentTypeASUTF8)
_ = json.NewEncoder(w).Encode(p.toASNote())
b, _ := json.Marshal(p.toASNote())
w.Header().Set(contentType, contentTypeASUTF8)
_, _ = writeMinified(w, contentTypeAS, b)
}
func (p *post) toASNote() *asNote {
@ -126,8 +128,6 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r
serveError(w, r, "Failed to marshal public key", http.StatusInternalServerError)
return
}
// Send JSON
w.Header().Add(contentType, contentTypeASUTF8)
asBlog := &asPerson{
Context: asContext,
Type: "Person",
@ -154,5 +154,7 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter, r
URL: appConfig.User.Picture,
}
}
_ = json.NewEncoder(w).Encode(asBlog)
jb, _ := json.Marshal(asBlog)
w.Header().Set(contentType, contentTypeASUTF8)
_, _ = writeMinified(w, contentTypeAS, jb)
}

61
api.go
View File

@ -1,61 +0,0 @@
package main
import (
"io/ioutil"
"net/http"
"strings"
)
// Not tested anymore
func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
blog := r.URL.Query().Get("blog")
path := r.URL.Query().Get("path")
section := r.URL.Query().Get("section")
slug := r.URL.Query().Get("slug")
alias := r.URL.Query().Get("alias")
defer func() {
_ = r.Body.Close()
}()
bodyContent, err := ioutil.ReadAll(r.Body)
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
p, aliases, err := parseHugoFile(string(bodyContent))
if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
p.Blog = blog
p.Path = path
p.Section = section
p.Slug = slug
err = p.create()
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
aliases = append(aliases, alias)
for i, alias := range aliases {
// Fix relativ paths
if !strings.HasPrefix(alias, "/") {
splittedPostPath := strings.Split(p.Path, "/")
alias = strings.TrimSuffix(p.Path, splittedPostPath[len(splittedPostPath)-1]) + alias
}
alias = strings.TrimSuffix(alias, "/")
if alias == p.Path {
alias = ""
}
aliases[i] = alias
}
if len(aliases) > 0 {
p.Parameters["aliases"] = aliases
err = p.replace(p.Path, p.Status)
if err != nil {
serveError(w, r, err.Error(), http.StatusBadRequest)
return
}
}
http.Redirect(w, r, p.fullURL(), http.StatusCreated)
}

View File

@ -16,7 +16,6 @@ type config struct {
Blogs map[string]*configBlog `mapstructure:"blogs"`
User *configUser `mapstructure:"user"`
Hooks *configHooks `mapstructure:"hooks"`
Hugo *configHugo `mapstructure:"hugo"`
Micropub *configMicropub `mapstructure:"micropub"`
PathRedirects []*configRegexRedirect `mapstructure:"pathRedirects"`
ActivityPub *configActivityPub `mapstructure:"activityPub"`
@ -151,15 +150,6 @@ type configHooks struct {
PostDelete []string `mapstructure:"postdelete"`
}
type configHugo struct {
Frontmatter []*frontmatter `mapstructure:"frontmatter"`
}
type frontmatter struct {
Meta string `mapstructure:"meta"`
Parameter string `mapstructure:"parameter"`
}
type configMicropub struct {
CategoryParam string `mapstructure:"categoryParam"`
ReplyParam string `mapstructure:"replyParam"`
@ -223,7 +213,6 @@ func initConfig() error {
viper.SetDefault("user.nick", "admin")
viper.SetDefault("user.password", "secret")
viper.SetDefault("hooks.shell", "/bin/bash")
viper.SetDefault("hugo.frontmatter", []*frontmatter{{Meta: "title", Parameter: "title"}, {Meta: "tags", Parameter: "tags"}})
viper.SetDefault("micropub.categoryParam", "tags")
viper.SetDefault("micropub.replyParam", "replylink")
viper.SetDefault("micropub.likeParam", "likelink")

View File

@ -3,7 +3,7 @@ package main
import (
"bytes"
"encoding/json"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"net/url"
@ -103,6 +103,6 @@ func editorMicropubPost(w http.ResponseWriter, r *http.Request, media bool) {
return
}
w.WriteHeader(result.StatusCode)
body, _ := ioutil.ReadAll(result.Body)
_, _ = w.Write(body)
_, _ = io.Copy(w, result.Body)
_ = result.Body.Close()
}

View File

@ -66,16 +66,17 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
})
}
var err error
var feedString, feedMediaType string
switch f {
case rssFeed:
w.Header().Set(contentType, "application/rss+xml; charset=utf-8")
err = feed.WriteRss(w)
feedMediaType = contentTypeRSS
feedString, err = feed.ToRss()
case atomFeed:
w.Header().Set(contentType, "application/atom+xml; charset=utf-8")
err = feed.WriteAtom(w)
feedMediaType = contentTypeATOM
feedString, err = feed.ToAtom()
case jsonFeed:
w.Header().Set(contentType, "application/feed+json; charset=utf-8")
err = feed.WriteJSON(w)
feedMediaType = contentTypeJSONFeed
feedString, err = feed.ToJSON()
default:
return
}
@ -84,4 +85,6 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set(contentType, feedMediaType+charsetUtf8Suffix)
_, _ = writeMinified(w, feedMediaType, []byte(feedString))
}

4
go.mod
View File

@ -20,7 +20,6 @@ require (
github.com/gorilla/feeds v1.1.1
github.com/gorilla/handlers v1.5.1
github.com/hashicorp/golang-lru v0.5.4
github.com/jeremywohl/flatten v1.0.1
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/joncrlsn/dque v0.0.0-20200702023911-3e80e3146ce5
github.com/klauspost/cpuid v1.3.1 // indirect
@ -43,8 +42,7 @@ require (
github.com/spf13/cast v1.3.1
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.12
github.com/tdewolff/parse/v2 v2.5.10 // indirect
github.com/tdewolff/minify/v2 v2.9.13
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.1-0.20201114172518-2cfc59fe05c2
github.com/yuin/goldmark v1.3.2

8
go.sum
View File

@ -142,8 +142,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/jeremywohl/flatten v1.0.1 h1:LrsxmB3hfwJuE+ptGOijix1PIfOoKLJ3Uee/mzbgtrs=
github.com/jeremywohl/flatten v1.0.1/go.mod h1:4AmD/VxjWcI5SRB0n6szE2A6s2fsNHDLO0nAlMHgfLQ=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -284,10 +282,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.12 h1:BKnbg3kcAuTLAhDeF3vSUxjvyj5lBRxaBvgxGCVLRRY=
github.com/tdewolff/minify/v2 v2.9.12/go.mod h1:yuntVVAFuGyi9VmiRoUqAYEQnFCGO929ytj2ITMZuB8=
github.com/tdewolff/parse/v2 v2.5.9 h1:9wCXRT3OcgYDNatgU+HUTOoGhE9WcnY5UxLNoJUe1yw=
github.com/tdewolff/parse/v2 v2.5.9/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/minify/v2 v2.9.13 h1:RrwQhgGoYBhKN/ezStGB+crU64wPK1ZE5Jmkl63lif0=
github.com/tdewolff/minify/v2 v2.9.13/go.mod h1:faNOp+awAoo+fhFHD+NAkBOaXBAvJI2X2SDERGKnARo=
github.com/tdewolff/parse/v2 v2.5.10 h1:vj35n+ljq8LuYUx436s4qB18wuwP7thrLv+t1syE39M=
github.com/tdewolff/parse/v2 v2.5.10/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=

189
http.go
View File

@ -23,6 +23,9 @@ const (
contentTypeWWWForm = "application/x-www-form-urlencoded"
contentTypeMultipartForm = "multipart/form-data"
contentTypeAS = "application/activity+json"
contentTypeRSS = "application/rss+xml"
contentTypeATOM = "application/atom+xml"
contentTypeJSONFeed = "application/feed+json"
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
@ -99,15 +102,9 @@ func buildHandler() (http.Handler, error) {
r.Mount("/debug", middleware.Profiler())
}
// API
r.Route("/api", func(apiRouter chi.Router) {
apiRouter.Use(middleware.NoCache, authMiddleware)
apiRouter.Post("/hugo", apiPostCreateHugo)
})
// Micropub
r.Route(micropubPath, func(mpRouter chi.Router) {
mpRouter.Use(checkIndieAuth, middleware.NoCache, minifier.Middleware)
mpRouter.Use(checkIndieAuth)
mpRouter.Get("/", serveMicropubQuery)
mpRouter.Post("/", serveMicropubPost)
mpRouter.Post(micropubMediaSubPath, serveMicropubMedia)
@ -115,7 +112,6 @@ func buildHandler() (http.Handler, error) {
// IndieAuth
r.Route("/indieauth", func(indieauthRouter chi.Router) {
indieauthRouter.Use(middleware.NoCache, minifier.Middleware)
indieauthRouter.Get("/", indieAuthRequest)
indieauthRouter.With(authMiddleware).Post("/accept", indieAuthAccept)
indieauthRouter.Post("/", indieAuthVerification)
@ -124,23 +120,26 @@ func buildHandler() (http.Handler, error) {
})
// ActivityPub and stuff
if appConfig.ActivityPub.Enabled {
if ap := appConfig.ActivityPub; ap != nil && ap.Enabled {
r.Post("/activitypub/inbox/{blog}", apHandleInbox)
r.Post("/activitypub/{blog}/inbox", apHandleInbox)
r.Get("/.well-known/webfinger", apHandleWebfinger)
r.With(cacheMiddleware).Get("/.well-known/webfinger", apHandleWebfinger)
r.With(cacheMiddleware).Get("/.well-known/host-meta", handleWellKnownHostMeta)
r.With(cacheMiddleware, minifier.Middleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover)
r.With(cacheMiddleware, minifier.Middleware).Get("/nodeinfo", serveNodeInfo)
r.With(cacheMiddleware).Get("/.well-known/nodeinfo", serveNodeInfoDiscover)
r.With(cacheMiddleware).Get("/nodeinfo", serveNodeInfo)
}
// Webmentions
r.Route(webmentionPath, func(webmentionRouter chi.Router) {
webmentionRouter.Use(middleware.NoCache)
webmentionRouter.Post("/", handleWebmention)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get("/", webmentionAdmin)
webmentionRouter.With(minifier.Middleware, authMiddleware).Get(paginationPath, webmentionAdmin)
webmentionRouter.With(authMiddleware).Post("/delete", webmentionAdminDelete)
webmentionRouter.With(authMiddleware).Post("/approve", webmentionAdminApprove)
webmentionRouter.Group(func(r chi.Router) {
// Authenticated routes
r.Use(authMiddleware)
r.Get("/", webmentionAdmin)
r.Get(paginationPath, webmentionAdmin)
r.Post("/delete", webmentionAdminDelete)
r.Post("/approve", webmentionAdminApprove)
})
})
// Posts
@ -148,39 +147,36 @@ func buildHandler() (http.Handler, error) {
if err != nil {
return nil, err
}
var postMW []func(http.Handler) http.Handler
if appConfig.ActivityPub.Enabled {
postMW = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware}
} else {
postMW = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
}
r.Group(func(r chi.Router) {
r.Use(manipulateAsPath, cacheMiddleware)
for _, path := range pp {
if path != "" {
r.With(postMW...).Get(path, servePost)
}
r.Get(path, servePost)
}
})
// Drafts
dp, err := allPostPaths(statusDraft)
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(authMiddleware, cacheMiddleware)
for _, path := range dp {
if path != "" {
r.With(middleware.NoCache, minifier.Middleware, authMiddleware).Get(path, servePost)
}
r.Get(path, servePost)
}
})
// Post aliases
allPostAliases, err := allPostAliases()
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
for _, path := range allPostAliases {
if path != "" {
r.With(cacheMiddleware).Get(path, servePostAlias)
}
r.Get(path, servePostAlias)
}
})
// Assets
for _, path := range allAssetPaths() {
@ -209,60 +205,79 @@ func buildHandler() (http.Handler, error) {
blogPath = ""
}
// Indexes, Feeds
r.Group(func(r chi.Router) {
})
// Sections
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
for _, section := range blogConfig.Sections {
if section.Name != "" {
path := blogPath + "/" + section.Name
handler := serveSection(blog, path, section)
r.With(cacheMiddleware, minifier.Middleware).Get(path, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(path+feedPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(path+paginationPath, handler)
secPath := blogPath + "/" + section.Name
handler := serveSection(blog, secPath, section)
r.Get(secPath, handler)
r.Get(secPath+feedPath, handler)
r.Get(secPath+paginationPath, handler)
}
}
})
// Taxonomies
for _, taxonomy := range blogConfig.Taxonomies {
if taxonomy.Name != "" {
path := blogPath + "/" + taxonomy.Name
r.With(cacheMiddleware, minifier.Middleware).Get(path, serveTaxonomy(blog, taxonomy))
values, err := allTaxonomyValues(blog, taxonomy.Name)
taxPath := blogPath + "/" + taxonomy.Name
taxValues, err := allTaxonomyValues(blog, taxonomy.Name)
if err != nil {
return nil, err
}
for _, tv := range values {
vPath := path + "/" + urlize(tv)
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
r.Get(taxPath, serveTaxonomy(blog, taxonomy))
for _, tv := range taxValues {
vPath := taxPath + "/" + urlize(tv)
handler := serveTaxonomyValue(blog, vPath, taxonomy, tv)
r.With(cacheMiddleware, minifier.Middleware).Get(vPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+feedPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(vPath+paginationPath, handler)
r.Get(vPath, handler)
r.Get(vPath+feedPath, handler)
r.Get(vPath+paginationPath, handler)
}
})
}
}
// Photos
if blogConfig.Photos != nil && blogConfig.Photos.Enabled {
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
photoPath := blogPath + blogConfig.Photos.Path
handler := servePhotos(blog, photoPath)
r.With(cacheMiddleware, minifier.Middleware).Get(photoPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(photoPath+paginationPath, handler)
r.Get(photoPath, handler)
r.Get(photoPath+feedPath, handler)
r.Get(photoPath+paginationPath, handler)
})
}
// Search
if blogConfig.Search != nil && blogConfig.Search.Enabled {
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
searchPath := blogPath + blogConfig.Search.Path
handler := serveSearch(blog, searchPath)
r.With(cacheMiddleware, minifier.Middleware).Get(searchPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Post(searchPath, handler)
r.Get(searchPath, handler)
r.Post(searchPath, handler)
searchResultPath := searchPath + "/" + searchPlaceholder
resultHandler := serveSearchResults(blog, searchResultPath)
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath, resultHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+feedPath, resultHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(searchResultPath+paginationPath, resultHandler)
r.Get(searchResultPath, resultHandler)
r.Get(searchResultPath+feedPath, resultHandler)
r.Get(searchResultPath+paginationPath, resultHandler)
})
}
// Stats
if blogConfig.BlogStats != nil && blogConfig.BlogStats.Enabled {
statsPath := blogPath + blogConfig.BlogStats.Path
r.With(cacheMiddleware, minifier.Middleware).Get(statsPath, serveBlogStats(blog, statsPath))
r.With(cacheMiddleware).Get(statsPath, serveBlogStats(blog, statsPath))
}
// Year / month archives
@ -270,60 +285,57 @@ func buildHandler() (http.Handler, error) {
if err != nil {
return nil, err
}
r.Group(func(r chi.Router) {
r.Use(cacheMiddleware)
for _, d := range dates {
// Year
yearPath := blogPath + "/" + fmt.Sprintf("%0004d", d.year)
yearHandler := serveDate(blog, yearPath, d.year, 0, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath, yearHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+feedPath, yearHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(yearPath+paginationPath, yearHandler)
r.Get(yearPath, yearHandler)
r.Get(yearPath+feedPath, yearHandler)
r.Get(yearPath+paginationPath, yearHandler)
// Specific month
monthPath := yearPath + "/" + fmt.Sprintf("%02d", d.month)
monthHandler := serveDate(blog, monthPath, d.year, d.month, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath, monthHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+feedPath, monthHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(monthPath+paginationPath, monthHandler)
r.Get(monthPath, monthHandler)
r.Get(monthPath+feedPath, monthHandler)
r.Get(monthPath+paginationPath, monthHandler)
// Specific day
dayPath := monthPath + "/" + fmt.Sprintf("%02d", d.day)
dayHandler := serveDate(blog, monthPath, d.year, d.month, d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath, dayHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+feedPath, dayHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(dayPath+paginationPath, dayHandler)
r.Get(dayPath, dayHandler)
r.Get(dayPath+feedPath, dayHandler)
r.Get(dayPath+paginationPath, dayHandler)
// Generic month
genericMonthPath := blogPath + "/x/" + fmt.Sprintf("%02d", d.month)
genericMonthHandler := serveDate(blog, genericMonthPath, 0, d.month, 0)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath, genericMonthHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+feedPath, genericMonthHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthPath+paginationPath, genericMonthHandler)
r.Get(genericMonthPath, genericMonthHandler)
r.Get(genericMonthPath+feedPath, genericMonthHandler)
r.Get(genericMonthPath+paginationPath, genericMonthHandler)
// Specific day
genericMonthDayPath := genericMonthPath + "/" + fmt.Sprintf("%02d", d.day)
genericMonthDayHandler := serveDate(blog, genericMonthDayPath, 0, d.month, d.day)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath, genericMonthDayHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+feedPath, genericMonthDayHandler)
r.With(cacheMiddleware, minifier.Middleware).Get(genericMonthDayPath+paginationPath, genericMonthDayHandler)
r.Get(genericMonthDayPath, genericMonthDayHandler)
r.Get(genericMonthDayPath+feedPath, genericMonthDayHandler)
r.Get(genericMonthDayPath+paginationPath, genericMonthDayHandler)
}
})
// Blog
if !blogConfig.PostAsHome {
var mw []func(http.Handler) http.Handler
if appConfig.ActivityPub.Enabled {
mw = []func(http.Handler) http.Handler{manipulateAsPath, cacheMiddleware, minifier.Middleware}
} else {
mw = []func(http.Handler) http.Handler{cacheMiddleware, minifier.Middleware}
}
handler := serveHome(blog, blogPath)
r.With(mw...).Get(fullBlogPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(fullBlogPath+feedPath, handler)
r.With(cacheMiddleware, minifier.Middleware).Get(blogPath+paginationPath, handler)
r.With(manipulateAsPath, cacheMiddleware).Get(fullBlogPath, handler)
r.With(cacheMiddleware).Get(fullBlogPath+feedPath, handler)
r.With(cacheMiddleware).Get(blogPath+paginationPath, handler)
}
// Custom pages
for _, cp := range blogConfig.CustomPages {
handler := serveCustomPage(blogConfig, cp)
if cp.Cache {
r.With(cacheMiddleware, minifier.Middleware).Get(cp.Path, handler)
r.With(cacheMiddleware).Get(cp.Path, handler)
} else {
r.With(minifier.Middleware).Get(cp.Path, handler)
r.Get(cp.Path, handler)
}
}
@ -338,7 +350,7 @@ func buildHandler() (http.Handler, error) {
// Editor
r.Route(blogPath+"/editor", func(mpRouter chi.Router) {
mpRouter.Use(middleware.NoCache, minifier.Middleware, authMiddleware)
mpRouter.Use(authMiddleware)
mpRouter.Get("/", serveEditor(blog))
mpRouter.Post("/", serveEditorPost(blog))
})
@ -347,25 +359,28 @@ func buildHandler() (http.Handler, error) {
if commentsConfig := blogConfig.Comments; commentsConfig != nil && commentsConfig.Enabled {
commentsPath := blogPath + "/comment"
r.Route(commentsPath, func(cr chi.Router) {
cr.With(cacheMiddleware, minifier.Middleware).Get("/{id:[0-9]+}", serveComment(blog))
cr.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment(blog))
cr.With(captchaMiddleware).Post("/", createComment(blog, commentsPath))
// Admin
cr.With(minifier.Middleware, authMiddleware).Get("/", commentsAdmin)
cr.With(authMiddleware).Post("/delete", commentsAdminDelete)
cr.Group(func(r chi.Router) {
r.Use(authMiddleware)
r.Get("/", commentsAdmin)
r.Post("/delete", commentsAdminDelete)
})
})
}
}
// Sitemap
r.With(cacheMiddleware, minifier.Middleware).Get(sitemapPath, serveSitemap)
r.With(cacheMiddleware).Get(sitemapPath, serveSitemap)
// Robots.txt - doesn't need cache, because it's too simple
r.Get("/robots.txt", serveRobotsTXT)
// Check redirects, then serve 404
r.With(cacheMiddleware, checkRegexRedirects, minifier.Middleware).NotFound(serve404)
r.With(cacheMiddleware, checkRegexRedirects).NotFound(serve404)
r.With(minifier.Middleware).MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) {
r.MethodNotAllowed(func(rw http.ResponseWriter, r *http.Request) {
serveError(rw, r, "", http.StatusMethodNotAllowed)
})

71
hugo.go
View File

@ -1,71 +0,0 @@
package main
import (
"strconv"
"strings"
"github.com/jeremywohl/flatten"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
)
func parseHugoFile(fileContent string) (p *post, aliases []string, e error) {
frontmatterSep := "---\n"
frontmatter := ""
if split := strings.Split(fileContent, frontmatterSep); len(split) > 2 {
frontmatter = split[1]
}
p = &post{
Content: strings.TrimPrefix(fileContent, frontmatterSep+frontmatter+frontmatterSep),
Parameters: map[string][]string{},
}
// Parse frontmatter
meta := map[string]interface{}{}
err := yaml.Unmarshal([]byte(frontmatter), &meta)
if err != nil {
return nil, nil, err
}
flat, err := flatten.Flatten(meta, "", flatten.DotStyle)
if err != nil {
return nil, nil, err
}
// Read dates
p.Published = cast.ToString(flat["date"])
p.Updated = cast.ToString(flat["lastmod"])
// Read parameters
for _, fm := range appConfig.Hugo.Frontmatter {
var values []string
for fk, value := range flat {
if strings.HasPrefix(fk, fm.Meta) {
trimmed := strings.TrimPrefix(fk, fm.Meta)
if len(trimmed) == 0 {
values = append(values, cast.ToString(value))
} else {
trimmed = strings.TrimPrefix(trimmed, ".")
if _, e := strconv.Atoi(trimmed); e == nil {
values = append(values, cast.ToString(value))
}
}
}
}
if len(values) > 0 {
p.Parameters[fm.Parameter] = values
}
}
// Parse redirects
for fk, value := range flat {
if strings.HasPrefix(fk, "aliases") {
trimmed := strings.TrimPrefix(fk, "aliases")
if len(trimmed) == 0 {
aliases = append(aliases, cast.ToString(value))
} else {
trimmed = strings.TrimPrefix(trimmed, ".")
if _, e := strconv.Atoi(trimmed); e == nil {
aliases = append(aliases, cast.ToString(value))
}
}
}
}
// Return post
return p, aliases, nil
}

View File

@ -134,16 +134,11 @@ func indieAuthVerification(w http.ResponseWriter, r *http.Request) {
serveError(w, r, "Authentication not valid", http.StatusForbidden)
return
}
res := &tokenResponse{
b, _ := json.Marshal(tokenResponse{
Me: appConfig.Server.PublicAddress,
}
w.Header().Add(contentType, contentTypeJSONUTF8)
err = json.NewEncoder(w).Encode(res)
if err != nil {
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
})
w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
}
func indieAuthToken(w http.ResponseWriter, r *http.Request) {
@ -159,13 +154,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) {
Me: appConfig.Server.PublicAddress,
ClientID: data.ClientID,
}
w.Header().Add(contentType, contentTypeJSONUTF8)
err = json.NewEncoder(w).Encode(res)
if err != nil {
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
b, _ := json.Marshal(res)
w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
return
} else if r.Method == http.MethodPost {
if err := r.ParseForm(); err != nil {
@ -216,13 +207,9 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) {
Scope: strings.Join(data.Scopes, " "),
Me: appConfig.Server.PublicAddress,
}
w.Header().Add(contentType, contentTypeJSONUTF8)
err = json.NewEncoder(w).Encode(res)
if err != nil {
w.Header().Del(contentType)
serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
b, _ := json.Marshal(res)
w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
return
}
serveError(w, r, "", http.StatusBadRequest)

View File

@ -25,11 +25,12 @@ type micropubConfig struct {
func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
switch r.URL.Query().Get("q") {
case "config":
w.Header().Add(contentType, contentTypeJSONUTF8)
w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(&micropubConfig{
b, _ := json.Marshal(&micropubConfig{
MediaEndpoint: appConfig.Server.PublicAddress + micropubPath + micropubMediaSubPath,
})
_, _ = writeMinified(w, contentTypeJSON, b)
case "source":
var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" {
@ -61,9 +62,10 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
mf = list
}
w.Header().Add(contentType, contentTypeJSONUTF8)
w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(mf)
b, _ := json.Marshal(mf)
_, _ = writeMinified(w, contentTypeJSON, b)
case "category":
allCategories := []string{}
for blog := range appConfig.Blogs {
@ -74,11 +76,12 @@ func serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
allCategories = append(allCategories, values...)
}
w.Header().Add(contentType, contentTypeJSONUTF8)
w.Header().Set(contentType, contentTypeJSONUTF8)
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
b, _ := json.Marshal(map[string]interface{}{
"categories": allCategories,
})
_, _ = writeMinified(w, contentTypeJSON, b)
default:
serve404(w, r)
}

View File

@ -1,6 +1,8 @@
package main
import (
"io"
"github.com/tdewolff/minify/v2"
mCss "github.com/tdewolff/minify/v2/css"
mHtml "github.com/tdewolff/minify/v2/html"
@ -17,7 +19,14 @@ func initMinify() {
minifier.AddFunc("text/css", mCss.Minify)
minifier.AddFunc("text/xml", mXml.Minify)
minifier.AddFunc("application/javascript", mJs.Minify)
minifier.AddFunc("application/rss+xml", mXml.Minify)
minifier.AddFunc("application/atom+xml", mXml.Minify)
minifier.AddFunc("application/feed+json", mJson.Minify)
minifier.AddFunc(contentTypeRSS, mXml.Minify)
minifier.AddFunc(contentTypeATOM, mXml.Minify)
minifier.AddFunc(contentTypeJSONFeed, mJson.Minify)
minifier.AddFunc(contentTypeAS, mJson.Minify)
}
func writeMinified(w io.Writer, mediatype string, b []byte) (int, error) {
mw := minifier.Writer(mediatype, w)
defer func() { mw.Close() }()
return mw.Write(b)
}

View File

@ -6,24 +6,23 @@ import (
)
func serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentType, contentTypeJSONUTF8)
nid := map[string]interface{}{
b, _ := json.Marshal(map[string]interface{}{
"links": []map[string]interface{}{
{
"href": appConfig.Server.PublicAddress + "/nodeinfo",
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.1",
},
},
}
_ = json.NewEncoder(w).Encode(&nid)
})
w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
}
func serveNodeInfo(w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentType, contentTypeJSONUTF8)
localPosts, _ := countPosts(&postsRequestConfig{
status: statusPublished,
})
nid := map[string]interface{}{
b, _ := json.Marshal(map[string]interface{}{
"version": "2.1",
"software": map[string]interface{}{
"name": "goblog",
@ -41,6 +40,7 @@ func serveNodeInfo(w http.ResponseWriter, r *http.Request) {
"webmention",
},
"metadata": map[string]interface{}{},
}
_ = json.NewEncoder(w).Encode(&nid)
})
w.Header().Set(contentType, contentTypeJSONUTF8)
_, _ = writeMinified(w, contentTypeJSON, b)
}

View File

@ -14,8 +14,10 @@ func allPostAliases() ([]string, error) {
for rows.Next() {
var path string
_ = rows.Scan(&path)
if path != "" {
aliases = append(aliases, path)
}
}
return aliases, nil
}

View File

@ -396,8 +396,10 @@ func allPostPaths(status postStatus) ([]string, error) {
for rows.Next() {
var path string
_ = rows.Scan(&path)
if path != "" {
postPaths = append(postPaths, path)
}
}
return postPaths, nil
}

View File

@ -264,8 +264,8 @@ func render(w http.ResponseWriter, template string, data *renderData) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Set content type (needed for minification middleware
// Set content type
w.Header().Set(contentType, contentTypeHTMLUTF8)
// Write buffered response
_, _ = w.Write(buffer.Bytes())
_, _ = writeMinified(w, contentTypeHTML, buffer.Bytes())
}

View File

@ -35,5 +35,5 @@ func serveSitemap(w http.ResponseWriter, r *http.Request) {
}
sm.Add(item)
}
_, _ = sm.WriteTo(w)
_, _ = sm.WriteTo(w) // Already minified
}

View File

@ -98,6 +98,6 @@ func serveAsset(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Cache-Control", "public,max-age=31536000,immutable")
w.Header().Set(contentType, af.contentType)
w.Header().Set(contentType, af.contentType+charsetUtf8Suffix)
_, _ = w.Write(af.body)
}