diff --git a/Dockerfile b/Dockerfile
index 4e59611..e00d5cc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@ RUN apk add --no-cache git gcc musl-dev
RUN apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main sqlite-dev
ADD *.go go.mod go.sum /app/
ENV GOFLAGS="-tags=linux,libsqlite3,sqlite_fts5"
-RUN go test -cover
+RUN go test -cover ./...
RUN go build -ldflags '-w -s' -o GoBlog
FROM alpine:3.13
diff --git a/activityPub.go b/activityPub.go
index 33482c1..c2546b7 100644
--- a/activityPub.go
+++ b/activityPub.go
@@ -15,6 +15,7 @@ import (
"strings"
"time"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/go-chi/chi/v5"
"github.com/go-fed/httpsig"
"github.com/spf13/cast"
@@ -90,7 +91,7 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
"links": []map[string]string{
{
"rel": "self",
- "type": contentTypeAS,
+ "type": contenttype.AS,
"href": a.apIri(blog),
},
{
@@ -100,8 +101,8 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) {
},
},
})
- w.Header().Set(contentType, "application/jrd+json"+charsetUtf8Suffix)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, "application/jrd+json"+contenttype.CharsetUtf8Suffix)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
}
func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) {
@@ -244,7 +245,7 @@ func apVerifySignature(r *http.Request) (*asPerson, string, int, error) {
}
func handleWellKnownHostMeta(w http.ResponseWriter, r *http.Request) {
- w.Header().Set(contentType, "application/xrd+xml"+charsetUtf8Suffix)
+ w.Header().Set(contentType, "application/xrd+xml"+contenttype.CharsetUtf8Suffix)
_, _ = w.Write([]byte(``))
}
@@ -253,7 +254,7 @@ func apGetRemoteActor(iri string) (*asPerson, int, error) {
if err != nil {
return nil, 0, err
}
- req.Header.Set("Accept", contentTypeAS)
+ req.Header.Set("Accept", contenttype.AS)
req.Header.Set(userAgent, appUserAgent)
resp, err := appHttpClient.Do(req)
if err != nil {
@@ -308,7 +309,7 @@ func (db *database) apRemoveInbox(inbox string) error {
func (a *goBlog) apPost(p *post) {
n := a.toASNote(p)
a.apSendToAllFollowers(p.Blog, map[string]interface{}{
- "@context": asContext,
+ "@context": []string{asContext},
"actor": a.apIri(a.cfg.Blogs[p.Blog]),
"id": a.fullPostURL(p),
"published": n.Published,
@@ -324,7 +325,7 @@ func (a *goBlog) apPost(p *post) {
func (a *goBlog) apUpdate(p *post) {
a.apSendToAllFollowers(p.Blog, map[string]interface{}{
- "@context": asContext,
+ "@context": []string{asContext},
"actor": a.apIri(a.cfg.Blogs[p.Blog]),
"id": a.fullPostURL(p),
"published": time.Now().Format("2006-01-02T15:04:05-07:00"),
@@ -335,7 +336,7 @@ func (a *goBlog) apUpdate(p *post) {
func (a *goBlog) apAnnounce(p *post) {
a.apSendToAllFollowers(p.Blog, map[string]interface{}{
- "@context": asContext,
+ "@context": []string{asContext},
"actor": a.apIri(a.cfg.Blogs[p.Blog]),
"id": a.fullPostURL(p) + "#announce",
"published": a.toASNote(p).Published,
@@ -346,7 +347,7 @@ func (a *goBlog) apAnnounce(p *post) {
func (a *goBlog) apDelete(p *post) {
a.apSendToAllFollowers(p.Blog, map[string]interface{}{
- "@context": asContext,
+ "@context": []string{asContext},
"actor": a.apIri(a.cfg.Blogs[p.Blog]),
"id": a.fullPostURL(p) + "#delete",
"type": "Delete",
@@ -383,7 +384,7 @@ func (a *goBlog) apAccept(blogName string, blog *configBlog, follow map[string]i
// remove @context from the inner activity
delete(follow, "@context")
accept := map[string]interface{}{
- "@context": asContext,
+ "@context": []string{asContext},
"to": follow["actor"],
"actor": a.apIri(blog),
"object": follow,
diff --git a/activityPubSending.go b/activityPubSending.go
index 6a146e4..3fe4663 100644
--- a/activityPubSending.go
+++ b/activityPubSending.go
@@ -11,6 +11,8 @@ import (
"net/http"
"net/url"
"time"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
type apRequest struct {
@@ -101,8 +103,8 @@ func (a *goBlog) apSendSigned(blogIri, to string, activity []byte) error {
r.Header.Set("Accept-Charset", "utf-8")
r.Header.Set("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
r.Header.Set(userAgent, appUserAgent)
- r.Header.Set("Accept", contentTypeASUTF8)
- r.Header.Set(contentType, contentTypeASUTF8)
+ r.Header.Set("Accept", contenttype.ASUTF8)
+ r.Header.Set(contentType, contenttype.ASUTF8)
r.Header.Set("Host", iri.Host)
// Sign request
a.apPostSignMutex.Lock()
diff --git a/activityStreams.go b/activityStreams.go
index 847d470..5a20e01 100644
--- a/activityStreams.go
+++ b/activityStreams.go
@@ -8,25 +8,26 @@ import (
"fmt"
"net/http"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/araddon/dateparse"
- "github.com/elnormous/contenttype"
+ ct "github.com/elnormous/contenttype"
)
-var asContext = []string{"https://www.w3.org/ns/activitystreams"}
-
-var asCheckMediaTypes = []contenttype.MediaType{
- contenttype.NewMediaType(contentTypeHTML),
- contenttype.NewMediaType(contentTypeAS),
- contenttype.NewMediaType("application/ld+json"),
-}
+const asContext = "https://www.w3.org/ns/activitystreams"
const asRequestKey requestContextKey = "asRequest"
+var asCheckMediaTypes = []ct.MediaType{
+ ct.NewMediaType(contenttype.HTML),
+ ct.NewMediaType(contenttype.AS),
+ ct.NewMediaType(contenttype.LDJSON),
+}
+
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 {
// Check if accepted media type is not HTML
- if mt, _, err := contenttype.GetAcceptableMediaType(r, asCheckMediaTypes); err == nil && mt.String() != asCheckMediaTypes[0].String() {
+ if mt, _, err := ct.GetAcceptableMediaType(r, asCheckMediaTypes); err == nil && mt.String() != asCheckMediaTypes[0].String() {
next.ServeHTTP(rw, r.WithContext(context.WithValue(r.Context(), asRequestKey, true)))
return
}
@@ -89,29 +90,29 @@ type asEndpoints struct {
func (a *goBlog) serveActivityStreamsPost(p *post, w http.ResponseWriter) {
b, _ := json.Marshal(a.toASNote(p))
- w.Header().Set(contentType, contentTypeASUTF8)
- _, _ = writeMinified(w, contentTypeAS, b)
+ w.Header().Set(contentType, contenttype.ASUTF8)
+ _, _ = a.min.Write(w, contenttype.AS, b)
}
func (a *goBlog) toASNote(p *post) *asNote {
// Create a Note object
as := &asNote{
- Context: asContext,
+ Context: []string{asContext},
To: []string{"https://www.w3.org/ns/activitystreams#Public"},
- MediaType: contentTypeHTML,
+ MediaType: contenttype.HTML,
ID: a.fullPostURL(p),
URL: a.fullPostURL(p),
AttributedTo: a.apIri(a.cfg.Blogs[p.Blog]),
}
// Name and Type
- if title := p.title(); title != "" {
+ if title := p.Title(); title != "" {
as.Name = title
as.Type = "Article"
} else {
as.Type = "Note"
}
// Content
- as.Content = string(a.absoluteHTML(p))
+ as.Content = string(a.absolutePostHTML(p))
// Attachments
if images := p.Parameters[a.cfg.Micropub.PhotoParam]; len(images) > 0 {
for _, image := range images {
@@ -158,7 +159,7 @@ func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *htt
return
}
asBlog := &asPerson{
- Context: asContext,
+ Context: []string{asContext},
Type: "Person",
ID: a.apIri(b),
URL: a.apIri(b),
@@ -184,6 +185,6 @@ func (a *goBlog) serveActivityStreams(blog string, w http.ResponseWriter, r *htt
}
}
jb, _ := json.Marshal(asBlog)
- w.Header().Set(contentType, contentTypeASUTF8)
- _, _ = writeMinified(w, contentTypeAS, jb)
+ w.Header().Set(contentType, contenttype.ASUTF8)
+ _, _ = a.min.Write(w, contenttype.AS, jb)
}
diff --git a/app.go b/app.go
index c9b3c40..3b1495e 100644
--- a/app.go
+++ b/app.go
@@ -6,6 +6,7 @@ import (
"net/http"
"sync"
+ "git.jlel.se/jlelse/GoBlog/pkgs/minify"
shutdowner "git.jlel.se/jlelse/go-shutdowner"
ts "git.jlel.se/jlelse/template-strings"
"github.com/go-chi/chi/v5"
@@ -27,6 +28,8 @@ type goBlog struct {
assetFiles map[string]*assetFile
// Blogroll
blogrollCacheGroup singleflight.Group
+ // Blogstats
+ blogStatsCacheGroup singleflight.Group
// Cache
cache *cache
// Config
@@ -37,6 +40,9 @@ type goBlog struct {
pPostHooks []postHookFunc
pUpdateHooks []postHookFunc
pDeleteHooks []postHookFunc
+ hourlyHooks []hourlyHookFunc
+ // HTTP
+ cspDomains string
// HTTP Routers
d *dynamicHandler
privateMode bool
@@ -53,6 +59,7 @@ type goBlog struct {
setBlogMiddlewares map[string]func(http.Handler) http.Handler
sectionMiddlewares map[string]func(http.Handler) http.Handler
taxonomyMiddlewares map[string]func(http.Handler) http.Handler
+ taxValueMiddlewares map[string]func(http.Handler) http.Handler
photosMiddlewares map[string]func(http.Handler) http.Handler
searchMiddlewares map[string]func(http.Handler) http.Handler
customPagesMiddlewares map[string]func(http.Handler) http.Handler
@@ -61,6 +68,8 @@ type goBlog struct {
logf *rotatelogs.RotateLogs
// Markdown
md, absoluteMd goldmark.Markdown
+ // Minify
+ min minify.Minifier
// Regex Redirects
regexRedirects []*regexRedirect
// Rendering
diff --git a/authentication.go b/authentication.go
index 7e132d3..2496adc 100644
--- a/authentication.go
+++ b/authentication.go
@@ -8,6 +8,7 @@ import (
"io"
"net/http"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/pquerna/otp/totp"
)
@@ -102,7 +103,7 @@ func (a *goBlog) checkLogin(w http.ResponseWriter, r *http.Request) bool {
if r.Method != http.MethodPost {
return false
}
- if r.Header.Get(contentType) != contentTypeWWWForm {
+ if r.Header.Get(contentType) != contenttype.WWWForm {
return false
}
if r.FormValue("loginaction") != "login" {
diff --git a/blogroll.go b/blogroll.go
index eefccb8..cbdee07 100644
--- a/blogroll.go
+++ b/blogroll.go
@@ -10,6 +10,7 @@ import (
"strings"
"time"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/kaorimatz/go-opml"
servertiming "github.com/mitchellh/go-server-timing"
"github.com/thoas/go-funk"
@@ -55,14 +56,14 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
if a.cfg.Cache != nil && a.cfg.Cache.Enable {
setInternalCacheExpirationHeader(w, r, int(a.cfg.Cache.Expiration))
}
- w.Header().Set(contentType, contentTypeXMLUTF8)
+ w.Header().Set(contentType, contenttype.XMLUTF8)
var opmlBytes bytes.Buffer
_ = opml.Render(&opmlBytes, &opml.OPML{
Version: "2.0",
DateCreated: time.Now().UTC(),
Outlines: outlines.([]*opml.Outline),
})
- _, _ = writeMinified(w, contentTypeXML, opmlBytes.Bytes())
+ _, _ = a.min.Write(w, contenttype.XML, opmlBytes.Bytes())
}
func (a *goBlog) getBlogrollOutlines(blog string) ([]*opml.Outline, error) {
diff --git a/blogstats.go b/blogstats.go
index 992c0e7..3acfebe 100644
--- a/blogstats.go
+++ b/blogstats.go
@@ -5,8 +5,6 @@ import (
"encoding/json"
"log"
"net/http"
-
- "golang.org/x/sync/singleflight"
)
func (a *goBlog) initBlogStats() {
@@ -31,11 +29,9 @@ func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
})
}
-var blogStatsCacheGroup singleflight.Group
-
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string)
- data, err, _ := blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
+ data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
return a.db.getBlogStats(blog)
})
if err != nil {
diff --git a/captcha.go b/captcha.go
index 0c82d21..f47ad1c 100644
--- a/captcha.go
+++ b/captcha.go
@@ -7,6 +7,7 @@ import (
"io"
"net/http"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/dchest/captcha"
)
@@ -53,7 +54,7 @@ func (a *goBlog) checkCaptcha(w http.ResponseWriter, r *http.Request) bool {
if r.Method != http.MethodPost {
return false
}
- if r.Header.Get(contentType) != contentTypeWWWForm {
+ if r.Header.Get(contentType) != contenttype.WWWForm {
return false
}
if r.FormValue("captchaaction") != "captcha" {
diff --git a/check.go b/check.go
index 14f042e..deb0b70 100644
--- a/check.go
+++ b/check.go
@@ -90,7 +90,7 @@ func (a *goBlog) getExternalLinks(posts []*post, linkChan chan<- stringPair) err
wg.Add(1)
go func(p *post) {
defer wg.Done()
- links, _ := allLinksFromHTMLString(string(a.absoluteHTML(p)), a.fullPostURL(p))
+ links, _ := allLinksFromHTMLString(string(a.absolutePostHTML(p)), a.fullPostURL(p))
for _, link := range links {
linkChan <- stringPair{a.fullPostURL(p), link}
}
diff --git a/database.go b/database.go
index 00a2ea5..cafbc6e 100644
--- a/database.go
+++ b/database.go
@@ -6,6 +6,7 @@ import (
"errors"
"log"
"os"
+ "sync"
sqlite "github.com/mattn/go-sqlite3"
"github.com/schollz/sqlite3dump"
@@ -19,6 +20,7 @@ type database struct {
stmts map[string]*sql.Stmt
g singleflight.Group
pc singleflight.Group
+ pcm sync.Mutex
}
func (a *goBlog) initDatabase() (err error) {
@@ -38,7 +40,7 @@ func (a *goBlog) initDatabase() (err error) {
}
})
if a.cfg.Db.DumpFile != "" {
- hourlyHooks = append(hourlyHooks, func() {
+ a.hourlyHooks = append(a.hourlyHooks, func() {
db.dump(a.cfg.Db.DumpFile)
})
db.dump(a.cfg.Db.DumpFile)
diff --git a/editor.go b/editor.go
index bf78f95..dfece7c 100644
--- a/editor.go
+++ b/editor.go
@@ -7,6 +7,8 @@ import (
"net/http"
"net/http/httptest"
"net/url"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
const editorPath = "/editor"
@@ -71,7 +73,7 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
- req.Header.Set(contentType, contentTypeJSON)
+ req.Header.Set(contentType, contenttype.JSON)
a.editorMicropubPost(w, req, false)
case "upload":
a.editorMicropubPost(w, r, true)
diff --git a/errors.go b/errors.go
index 53f25a4..8c93f2a 100644
--- a/errors.go
+++ b/errors.go
@@ -4,7 +4,8 @@ import (
"fmt"
"net/http"
- "github.com/elnormous/contenttype"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
+ ct "github.com/elnormous/contenttype"
)
type errorData struct {
@@ -20,12 +21,12 @@ func (a *goBlog) serveNotAllowed(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, "", http.StatusMethodNotAllowed)
}
-var errorCheckMediaTypes = []contenttype.MediaType{
- contenttype.NewMediaType(contentTypeHTML),
+var errorCheckMediaTypes = []ct.MediaType{
+ ct.NewMediaType(contenttype.HTML),
}
func (a *goBlog) serveError(w http.ResponseWriter, r *http.Request, message string, status int) {
- if mt, _, err := contenttype.GetAcceptableMediaType(r, errorCheckMediaTypes); err != nil || mt.String() != errorCheckMediaTypes[0].String() {
+ if mt, _, err := ct.GetAcceptableMediaType(r, errorCheckMediaTypes); err != nil || mt.String() != errorCheckMediaTypes[0].String() {
// Request doesn't accept HTML
http.Error(w, message, status)
return
diff --git a/feeds.go b/feeds.go
index ae6b4e8..37ed41f 100644
--- a/feeds.go
+++ b/feeds.go
@@ -5,6 +5,7 @@ import (
"strings"
"time"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/araddon/dateparse"
"github.com/gorilla/feeds"
)
@@ -55,11 +56,11 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
}
}
feed.Add(&feeds.Item{
- Title: p.title(),
+ Title: p.Title(),
Link: &feeds.Link{Href: a.fullPostURL(p)},
- Description: a.summary(p),
+ Description: a.postSummary(p),
Id: p.Path,
- Content: string(a.absoluteHTML(p)),
+ Content: string(a.absolutePostHTML(p)),
Created: created,
Updated: updated,
Enclosure: enc,
@@ -69,13 +70,13 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
var feedString, feedMediaType string
switch f {
case rssFeed:
- feedMediaType = contentTypeRSS
+ feedMediaType = contenttype.RSS
feedString, err = feed.ToRss()
case atomFeed:
- feedMediaType = contentTypeATOM
+ feedMediaType = contenttype.ATOM
feedString, err = feed.ToAtom()
case jsonFeed:
- feedMediaType = contentTypeJSONFeed
+ feedMediaType = contenttype.JSONFeed
feedString, err = feed.ToJSON()
default:
return
@@ -85,6 +86,6 @@ func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
- w.Header().Set(contentType, feedMediaType+charsetUtf8Suffix)
- _, _ = writeMinified(w, feedMediaType, []byte(feedString))
+ w.Header().Set(contentType, feedMediaType+contenttype.CharsetUtf8Suffix)
+ _, _ = a.min.Write(w, feedMediaType, []byte(feedString))
}
diff --git a/reverseGeo.go b/geo.go
similarity index 80%
rename from reverseGeo.go
rename to geo.go
index 77d796d..66405ea 100644
--- a/reverseGeo.go
+++ b/geo.go
@@ -7,12 +7,16 @@ import (
"net/url"
"strings"
+ gogeouri "git.jlel.se/jlelse/go-geouri"
geojson "github.com/paulmach/go.geojson"
"github.com/thoas/go-funk"
)
-func (db *database) geoTitle(lat, lon float64, lang string) string {
- ba, err := db.photonReverse(lat, lon, lang)
+func (db *database) geoTitle(g *gogeouri.Geo, lang string) string {
+ if name, ok := g.Parameters["name"]; ok && len(name) > 0 && name[0] != "" {
+ return name[0]
+ }
+ ba, err := db.photonReverse(g.Latitude, g.Longitude, lang)
if err != nil {
return ""
}
@@ -65,3 +69,7 @@ func (db *database) photonReverse(lat, lon float64, lang string) ([]byte, error)
_ = db.cachePersistently(cacheKey, ba)
return ba, nil
}
+
+func geoOSMLink(g *gogeouri.Geo) string {
+ return fmt.Sprintf("https://www.openstreetmap.org/?mlat=%v&mlon=%v", g.Latitude, g.Longitude)
+}
diff --git a/hooks.go b/hooks.go
index 2b95aa7..e4cdb3e 100644
--- a/hooks.go
+++ b/hooks.go
@@ -78,7 +78,7 @@ func (cfg *configHooks) executeTemplateCommand(hookType string, tmpl string, dat
executeHookCommand(hookType, cfg.Shell, cmd)
}
-var hourlyHooks = []func(){}
+type hourlyHookFunc func()
func (a *goBlog) startHourlyHooks() {
cfg := a.cfg.Hooks
@@ -88,14 +88,14 @@ func (a *goBlog) startHourlyHooks() {
f := func() {
executeHookCommand("hourly", cfg.Shell, c)
}
- hourlyHooks = append(hourlyHooks, f)
+ a.hourlyHooks = append(a.hourlyHooks, f)
}
// When there are hooks, start ticker
- if len(hourlyHooks) > 0 {
+ if len(a.hourlyHooks) > 0 {
// Wait for next full hour
tr := time.AfterFunc(time.Until(time.Now().Truncate(time.Hour).Add(time.Hour)), func() {
// Execute once
- for _, f := range hourlyHooks {
+ for _, f := range a.hourlyHooks {
go f()
}
// Start ticker and execute regularly
@@ -105,7 +105,7 @@ func (a *goBlog) startHourlyHooks() {
log.Println("Stopped hourly hooks")
})
for range ticker.C {
- for _, f := range hourlyHooks {
+ for _, f := range a.hourlyHooks {
go f()
}
}
diff --git a/http.go b/http.go
index 54c0078..e820135 100644
--- a/http.go
+++ b/http.go
@@ -21,25 +21,7 @@ import (
)
const (
- contentType = "Content-Type"
-
- charsetUtf8Suffix = "; charset=utf-8"
-
- contentTypeHTML = "text/html"
- contentTypeXML = "text/xml"
- contentTypeJSON = "application/json"
- 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
- contentTypeXMLUTF8 = contentTypeXML + charsetUtf8Suffix
- contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
- contentTypeASUTF8 = contentTypeAS + charsetUtf8Suffix
-
+ contentType = "Content-Type"
userAgent = "User-Agent"
appUserAgent = "GoBlog"
)
@@ -241,6 +223,7 @@ func (a *goBlog) buildStaticHandlersRouters() error {
a.setBlogMiddlewares = map[string]func(http.Handler) http.Handler{}
a.sectionMiddlewares = map[string]func(http.Handler) http.Handler{}
a.taxonomyMiddlewares = map[string]func(http.Handler) http.Handler{}
+ a.taxValueMiddlewares = map[string]func(http.Handler) http.Handler{}
a.photosMiddlewares = map[string]func(http.Handler) http.Handler{}
a.searchMiddlewares = map[string]func(http.Handler) http.Handler{}
a.customPagesMiddlewares = map[string]func(http.Handler) http.Handler{}
@@ -293,10 +276,6 @@ func (a *goBlog) buildStaticHandlersRouters() error {
return nil
}
-var (
- taxValueMiddlewares = map[string]func(http.Handler) http.Handler{}
-)
-
func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
r := chi.NewRouter()
@@ -436,14 +415,14 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
for _, tv := range taxValues {
r.Group(func(r chi.Router) {
vPath := taxPath + "/" + urlize(tv)
- if _, ok := taxValueMiddlewares[vPath]; !ok {
- taxValueMiddlewares[vPath] = middleware.WithValue(indexConfigKey, &indexConfig{
+ if _, ok := a.taxValueMiddlewares[vPath]; !ok {
+ a.taxValueMiddlewares[vPath] = middleware.WithValue(indexConfigKey, &indexConfig{
path: vPath,
tax: taxonomy,
taxValue: tv,
})
}
- r.Use(taxValueMiddlewares[vPath])
+ r.Use(a.taxValueMiddlewares[vPath])
r.Get(vPath, a.serveIndex)
r.Get(vPath+feedPath, a.serveIndex)
r.Get(vPath+paginationPath, a.serveIndex)
@@ -512,10 +491,9 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
r.Group(func(r chi.Router) {
r.Use(a.privateModeHandler...)
r.Use(sbm)
- blogBasePath := blogConfig.getRelativePath("")
- r.With(a.checkActivityStreamsRequest, a.cache.cacheMiddleware).Get(blogBasePath, a.serveHome)
- r.With(a.cache.cacheMiddleware).Get(blogBasePath+feedPath, a.serveHome)
- r.With(a.cache.cacheMiddleware).Get(blogBasePath+paginationPath, a.serveHome)
+ r.With(a.checkActivityStreamsRequest, a.cache.cacheMiddleware).Get(blogConfig.getRelativePath(""), a.serveHome)
+ r.With(a.cache.cacheMiddleware).Get(blogConfig.getRelativePath("")+feedPath, a.serveHome)
+ r.With(a.cache.cacheMiddleware).Get(blogConfig.getRelativePath(paginationPath), a.serveHome)
})
}
@@ -579,17 +557,15 @@ func (a *goBlog) buildDynamicRouter() (*chi.Mux, error) {
const blogContextKey requestContextKey = "blog"
const pathContextKey requestContextKey = "httpPath"
-var cspDomains = ""
-
func (a *goBlog) refreshCSPDomains() {
- cspDomains = ""
+ a.cspDomains = ""
if mp := a.cfg.Micropub.MediaStorage; mp != nil && mp.MediaURL != "" {
if u, err := url.Parse(mp.MediaURL); err == nil {
- cspDomains += " " + u.Hostname()
+ a.cspDomains += " " + u.Hostname()
}
}
if len(a.cfg.Server.CSPDomains) > 0 {
- cspDomains += " " + strings.Join(a.cfg.Server.CSPDomains, " ")
+ a.cspDomains += " " + strings.Join(a.cfg.Server.CSPDomains, " ")
}
}
@@ -601,7 +577,7 @@ func (a *goBlog) securityHeaders(next http.Handler) http.Handler {
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)
+ w.Header().Set("Content-Security-Policy", "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))
}
diff --git a/indieAuthServer.go b/indieAuthServer.go
index 2da8a6e..814b7f6 100644
--- a/indieAuthServer.go
+++ b/indieAuthServer.go
@@ -11,6 +11,7 @@ import (
"strings"
"time"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/spf13/cast"
)
@@ -137,8 +138,8 @@ func (a *goBlog) indieAuthVerification(w http.ResponseWriter, r *http.Request) {
b, _ := json.Marshal(tokenResponse{
Me: a.cfg.Server.PublicAddress,
})
- w.Header().Set(contentType, contentTypeJSONUTF8)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
}
func (a *goBlog) indieAuthToken(w http.ResponseWriter, r *http.Request) {
@@ -155,8 +156,8 @@ func (a *goBlog) indieAuthToken(w http.ResponseWriter, r *http.Request) {
ClientID: data.ClientID,
}
b, _ := json.Marshal(res)
- w.Header().Set(contentType, contentTypeJSONUTF8)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
return
} else if r.Method == http.MethodPost {
if err := r.ParseForm(); err != nil {
@@ -208,8 +209,8 @@ func (a *goBlog) indieAuthToken(w http.ResponseWriter, r *http.Request) {
Me: a.cfg.Server.PublicAddress,
}
b, _ := json.Marshal(res)
- w.Header().Set(contentType, contentTypeJSONUTF8)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
return
}
a.serveError(w, r, "", http.StatusBadRequest)
diff --git a/main.go b/main.go
index 070d019..5e123ab 100644
--- a/main.go
+++ b/main.go
@@ -10,13 +10,12 @@ import (
"github.com/pquerna/otp/totp"
)
-var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
-var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
-
func main() {
var err error
// Init CPU and memory profiling
+ cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`")
+ memprofile := flag.String("memprofile", "", "write memory profile to `file`")
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
diff --git a/markdown.go b/markdown.go
index beb0669..6df7fef 100644
--- a/markdown.go
+++ b/markdown.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ "html/template"
"strings"
marktag "git.jlel.se/jlelse/goldmark-mark"
@@ -54,6 +55,19 @@ func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []b
return buffer.Bytes(), err
}
+func (a *goBlog) renderMarkdownAsHTML(source string, absoluteLinks bool) (rendered template.HTML, err error) {
+ b, err := a.renderMarkdown(source, absoluteLinks)
+ if err != nil {
+ return "", err
+ }
+ return template.HTML(b), nil
+}
+
+func (a *goBlog) safeRenderMarkdownAsHTML(source string) template.HTML {
+ h, _ := a.renderMarkdownAsHTML(source, false)
+ return h
+}
+
func (a *goBlog) renderText(s string) string {
h, err := a.renderMarkdown(s, false)
if err != nil {
diff --git a/markdown_test.go b/markdown_test.go
index cc21af1..4dbd5a8 100644
--- a/markdown_test.go
+++ b/markdown_test.go
@@ -4,6 +4,8 @@ import (
"os"
"strings"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func Test_markdown(t *testing.T) {
@@ -65,6 +67,11 @@ func Test_markdown(t *testing.T) {
if renderedText != "This is text" {
t.Errorf("Wrong result, got \"%v\"", renderedText)
}
+
+ // Template func
+
+ renderedText = string(app.safeRenderMarkdownAsHTML("[Relative](/relative)"))
+ assert.Contains(t, renderedText, `href="/relative"`)
})
}
diff --git a/mediaCompression.go b/mediaCompression.go
index 52fbc57..8529d9d 100644
--- a/mediaCompression.go
+++ b/mediaCompression.go
@@ -8,6 +8,8 @@ import (
"io"
"net/http"
"os"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
const defaultCompressionWidth = 2000
@@ -34,7 +36,7 @@ func (a *goBlog) tinify(url string, config *configMicropubMedia) (location strin
return "", err
}
req.SetBasicAuth("api", config.TinifyKey)
- req.Header.Set(contentType, contentTypeJSON)
+ req.Header.Set(contentType, contenttype.JSON)
resp, err := appHttpClient.Do(req)
if err != nil {
return "", err
@@ -61,7 +63,7 @@ func (a *goBlog) tinify(url string, config *configMicropubMedia) (location strin
return "", err
}
downloadReq.SetBasicAuth("api", config.TinifyKey)
- downloadReq.Header.Set(contentType, contentTypeJSON)
+ downloadReq.Header.Set(contentType, contenttype.JSON)
downloadResp, err := appHttpClient.Do(downloadReq)
if err != nil {
return "", err
diff --git a/micropub.go b/micropub.go
index d8d16d8..37e53eb 100644
--- a/micropub.go
+++ b/micropub.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
"github.com/spf13/cast"
"gopkg.in/yaml.v3"
)
@@ -25,12 +26,12 @@ type micropubConfig struct {
func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
switch r.URL.Query().Get("q") {
case "config":
- w.Header().Set(contentType, contentTypeJSONUTF8)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK)
b, _ := json.Marshal(µpubConfig{
MediaEndpoint: a.getFullAddress(micropubPath + micropubMediaSubPath),
})
- _, _ = writeMinified(w, contentTypeJSON, b)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
case "source":
var mf interface{}
if urlString := r.URL.Query().Get("url"); urlString != "" {
@@ -62,10 +63,10 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
mf = list
}
- w.Header().Set(contentType, contentTypeJSONUTF8)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK)
b, _ := json.Marshal(mf)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
case "category":
allCategories := []string{}
for blog := range a.cfg.Blogs {
@@ -76,12 +77,12 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) {
}
allCategories = append(allCategories, values...)
}
- w.Header().Set(contentType, contentTypeJSONUTF8)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
w.WriteHeader(http.StatusOK)
b, _ := json.Marshal(map[string]interface{}{
"categories": allCategories,
})
- _, _ = writeMinified(w, contentTypeJSON, b)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
default:
a.serve404(w, r)
}
@@ -120,9 +121,9 @@ func (a *goBlog) toMfItem(p *post) *microformatItem {
func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var p *post
- if ct := r.Header.Get(contentType); strings.Contains(ct, contentTypeWWWForm) || strings.Contains(ct, contentTypeMultipartForm) {
+ if ct := r.Header.Get(contentType); strings.Contains(ct, contenttype.WWWForm) || strings.Contains(ct, contenttype.MultipartForm) {
var err error
- if strings.Contains(ct, contentTypeMultipartForm) {
+ if strings.Contains(ct, contenttype.MultipartForm) {
err = r.ParseMultipartForm(0)
} else {
err = r.ParseForm()
@@ -149,7 +150,7 @@ func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return
}
- } else if strings.Contains(ct, contentTypeJSON) {
+ } else if strings.Contains(ct, contenttype.JSON) {
parsedMfItem := µformatItem{}
err := json.NewDecoder(r.Body).Decode(parsedMfItem)
if err != nil {
diff --git a/micropubMedia.go b/micropubMedia.go
index 9869ca8..a056a75 100644
--- a/micropubMedia.go
+++ b/micropubMedia.go
@@ -11,6 +11,8 @@ import (
"path/filepath"
"sort"
"strings"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
const micropubMediaSubPath = "/media"
@@ -20,7 +22,7 @@ func (a *goBlog) serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
a.serveError(w, r, "media scope missing", http.StatusForbidden)
return
}
- if ct := r.Header.Get(contentType); !strings.Contains(ct, contentTypeMultipartForm) {
+ if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.MultipartForm) {
a.serveError(w, r, "wrong content-type", http.StatusBadRequest)
return
}
diff --git a/minify.go b/minify.go
deleted file mode 100644
index 6b51b59..0000000
--- a/minify.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package main
-
-import (
- "io"
- "sync"
-
- "github.com/tdewolff/minify/v2"
- mCss "github.com/tdewolff/minify/v2/css"
- mHtml "github.com/tdewolff/minify/v2/html"
- mJs "github.com/tdewolff/minify/v2/js"
- mJson "github.com/tdewolff/minify/v2/json"
- mXml "github.com/tdewolff/minify/v2/xml"
-)
-
-var (
- initMinify sync.Once
- minifier *minify.M
-)
-
-func getMinifier() *minify.M {
- initMinify.Do(func() {
- minifier = minify.New()
- minifier.AddFunc(contentTypeHTML, mHtml.Minify)
- minifier.AddFunc("text/css", mCss.Minify)
- minifier.AddFunc(contentTypeXML, mXml.Minify)
- minifier.AddFunc("application/javascript", mJs.Minify)
- minifier.AddFunc(contentTypeRSS, mXml.Minify)
- minifier.AddFunc(contentTypeATOM, mXml.Minify)
- minifier.AddFunc(contentTypeJSONFeed, mJson.Minify)
- minifier.AddFunc(contentTypeAS, mJson.Minify)
- })
- return minifier
-}
-
-func writeMinified(w io.Writer, mediatype string, b []byte) (int, error) {
- mw := getMinifier().Writer(mediatype, w)
- defer func() { _ = mw.Close() }()
- return mw.Write(b)
-}
diff --git a/nodeinfo.go b/nodeinfo.go
index 9b34b34..0f3204c 100644
--- a/nodeinfo.go
+++ b/nodeinfo.go
@@ -3,6 +3,8 @@ package main
import (
"encoding/json"
"net/http"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
@@ -14,8 +16,8 @@ func (a *goBlog) serveNodeInfoDiscover(w http.ResponseWriter, r *http.Request) {
},
},
})
- w.Header().Set(contentType, contentTypeJSONUTF8)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
}
func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) {
@@ -41,6 +43,6 @@ func (a *goBlog) serveNodeInfo(w http.ResponseWriter, r *http.Request) {
},
"metadata": map[string]interface{}{},
})
- w.Header().Set(contentType, contentTypeJSONUTF8)
- _, _ = writeMinified(w, contentTypeJSON, b)
+ w.Header().Set(contentType, contenttype.JSONUTF8)
+ _, _ = a.min.Write(w, contenttype.JSON, b)
}
diff --git a/opensearch.go b/opensearch.go
index 51329bc..feccc2a 100644
--- a/opensearch.go
+++ b/opensearch.go
@@ -3,6 +3,8 @@ package main
import (
"fmt"
"net/http"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
@@ -17,7 +19,7 @@ func (a *goBlog) serveOpenSearch(w http.ResponseWriter, r *http.Request) {
"",
title, title, sURL, sURL)
w.Header().Set(contentType, "application/opensearchdescription+xml")
- _, _ = writeMinified(w, contentTypeXML, []byte(xml))
+ _, _ = a.min.Write(w, contenttype.XML, []byte(xml))
}
func openSearchUrl(b *configBlog) string {
diff --git a/paths.go b/paths.go
index 0499e85..d6bcd0b 100644
--- a/paths.go
+++ b/paths.go
@@ -43,3 +43,9 @@ func (cfg *configServer) getFullAddress(path string) string {
}
return pa + path
}
+
+// Rendering funcs
+
+func (blog *configBlog) RelativePath(path string) string {
+ return blog.getRelativePath(path)
+}
diff --git a/pkgs/contenttype/contenttype.go b/pkgs/contenttype/contenttype.go
new file mode 100644
index 0000000..9460410
--- /dev/null
+++ b/pkgs/contenttype/contenttype.go
@@ -0,0 +1,25 @@
+package contenttype
+
+// This package contains constants for a few content types used in GoBlog
+
+const (
+ CharsetUtf8Suffix = "; charset=utf-8"
+
+ AS = "application/activity+json"
+ ATOM = "application/atom+xml"
+ CSS = "text/css"
+ HTML = "text/html"
+ JS = "application/javascript"
+ JSON = "application/json"
+ JSONFeed = "application/feed+json"
+ LDJSON = "application/ld+json"
+ MultipartForm = "multipart/form-data"
+ RSS = "application/rss+xml"
+ WWWForm = "application/x-www-form-urlencoded"
+ XML = "text/xml"
+
+ ASUTF8 = AS + CharsetUtf8Suffix
+ HTMLUTF8 = HTML + CharsetUtf8Suffix
+ JSONUTF8 = JSON + CharsetUtf8Suffix
+ XMLUTF8 = XML + CharsetUtf8Suffix
+)
diff --git a/pkgs/minify/minify.go b/pkgs/minify/minify.go
new file mode 100644
index 0000000..6c7989d
--- /dev/null
+++ b/pkgs/minify/minify.go
@@ -0,0 +1,45 @@
+package minify
+
+import (
+ "io"
+ "sync"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
+ "github.com/tdewolff/minify/v2"
+ mCss "github.com/tdewolff/minify/v2/css"
+ mHtml "github.com/tdewolff/minify/v2/html"
+ mJs "github.com/tdewolff/minify/v2/js"
+ mJson "github.com/tdewolff/minify/v2/json"
+ mXml "github.com/tdewolff/minify/v2/xml"
+)
+
+type Minifier struct {
+ i sync.Once
+ m *minify.M
+}
+
+func (m *Minifier) init() {
+ m.i.Do(func() {
+ m.m = minify.New()
+ m.m.AddFunc(contenttype.HTML, mHtml.Minify)
+ m.m.AddFunc(contenttype.CSS, mCss.Minify)
+ m.m.AddFunc(contenttype.XML, mXml.Minify)
+ m.m.AddFunc(contenttype.JS, mJs.Minify)
+ m.m.AddFunc(contenttype.RSS, mXml.Minify)
+ m.m.AddFunc(contenttype.ATOM, mXml.Minify)
+ m.m.AddFunc(contenttype.JSONFeed, mJson.Minify)
+ m.m.AddFunc(contenttype.AS, mJson.Minify)
+ })
+}
+
+func (m *Minifier) Get() *minify.M {
+ m.init()
+ return m.m
+}
+
+func (m *Minifier) Write(w io.Writer, mediatype string, b []byte) (int, error) {
+ m.init()
+ mw := m.m.Writer(mediatype, w)
+ defer func() { _ = mw.Close() }()
+ return mw.Write(b)
+}
diff --git a/pkgs/minify/minify_test.go b/pkgs/minify/minify_test.go
new file mode 100644
index 0000000..2a56c1e
--- /dev/null
+++ b/pkgs/minify/minify_test.go
@@ -0,0 +1,12 @@
+package minify
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_minify(t *testing.T) {
+ var min Minifier
+ assert.NotNil(t, min.Get())
+}
diff --git a/postsDb.go b/postsDb.go
index d2afa0e..f229eaf 100644
--- a/postsDb.go
+++ b/postsDb.go
@@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"strings"
- "sync"
"text/template"
"time"
@@ -122,8 +121,6 @@ type postCreationOptions struct {
oldStatus postStatus
}
-var postCreationMutex sync.Mutex
-
func (a *goBlog) createOrReplacePost(p *post, o *postCreationOptions) error {
// Check post
if err := a.checkPost(p); err != nil {
@@ -148,8 +145,8 @@ func (a *goBlog) createOrReplacePost(p *post, o *postCreationOptions) error {
// Save check post to database
func (db *database) savePost(p *post, o *postCreationOptions) error {
// Prevent bad things
- postCreationMutex.Lock()
- defer postCreationMutex.Unlock()
+ db.pcm.Lock()
+ defer db.pcm.Unlock()
// Check if path is already in use
if o.new || (p.Path != o.oldPath) {
// Post is new or post path was changed
diff --git a/postsDb_test.go b/postsDb_test.go
index 8efa234..cefb297 100644
--- a/postsDb_test.go
+++ b/postsDb_test.go
@@ -53,7 +53,7 @@ func Test_postsDb(t *testing.T) {
is.Equal("en", p.Blog)
is.Equal("test", p.Section)
is.Equal(statusDraft, p.Status)
- is.Equal("Title", p.title())
+ is.Equal("Title", p.Title())
// Check number of post paths
pp, err := app.db.allPostPaths(statusDraft)
diff --git a/postsFuncs.go b/postsFuncs.go
index cea3a9f..e70a08e 100644
--- a/postsFuncs.go
+++ b/postsFuncs.go
@@ -4,8 +4,11 @@ import (
"html/template"
"log"
"strings"
+ "time"
+ gogeouri "git.jlel.se/jlelse/go-geouri"
"github.com/PuerkitoBio/goquery"
+ "github.com/araddon/dateparse"
)
func (a *goBlog) fullPostURL(p *post) string {
@@ -23,6 +26,14 @@ func (a *goBlog) shortPostURL(p *post) string {
return a.getFullAddress(s)
}
+func postParameter(p *post, parameter string) []string {
+ return p.Parameters[parameter]
+}
+
+func postHasParameter(p *post, parameter string) bool {
+ return len(p.Parameters[parameter]) > 0
+}
+
func (p *post) firstParameter(parameter string) (result string) {
if pp := p.Parameters[parameter]; len(pp) > 0 {
result = pp[0]
@@ -30,11 +41,11 @@ func (p *post) firstParameter(parameter string) (result string) {
return
}
-func (p *post) title() string {
- return p.firstParameter("title")
+func firstPostParameter(p *post, parameter string) string {
+ return p.firstParameter(parameter)
}
-func (a *goBlog) html(p *post) template.HTML {
+func (a *goBlog) postHtml(p *post) template.HTML {
if p.rendered != "" {
return p.rendered
}
@@ -47,7 +58,7 @@ func (a *goBlog) html(p *post) template.HTML {
return p.rendered
}
-func (a *goBlog) absoluteHTML(p *post) template.HTML {
+func (a *goBlog) absolutePostHTML(p *post) template.HTML {
if p.absoluteRendered != "" {
return p.absoluteRendered
}
@@ -62,12 +73,12 @@ func (a *goBlog) absoluteHTML(p *post) template.HTML {
const summaryDivider = ""
-func (a *goBlog) summary(p *post) (summary string) {
+func (a *goBlog) postSummary(p *post) (summary string) {
summary = p.firstParameter("summary")
if summary != "" {
return
}
- html := string(a.html(p))
+ html := string(a.postHtml(p))
if splitted := strings.Split(html, summaryDivider); len(splitted) > 1 {
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(splitted[0]))
summary = doc.Text()
@@ -78,7 +89,7 @@ func (a *goBlog) summary(p *post) (summary string) {
return
}
-func (a *goBlog) translations(p *post) []*post {
+func (a *goBlog) postTranslations(p *post) []*post {
translationkey := p.firstParameter("translationkey")
if translationkey == "" {
return nil
@@ -105,3 +116,30 @@ func (a *goBlog) translations(p *post) []*post {
func (p *post) isPublishedSectionPost() bool {
return p.Published != "" && p.Section != "" && p.Status == statusPublished
}
+
+// Public because of rendering
+
+func (p *post) Title() string {
+ return p.firstParameter("title")
+}
+
+func (p *post) GeoURI() *gogeouri.Geo {
+ loc := p.firstParameter("location")
+ if loc == "" {
+ return nil
+ }
+ g, _ := gogeouri.Parse(loc)
+ return g
+}
+
+func (p *post) Old() bool {
+ pub := p.Published
+ if pub == "" {
+ return false
+ }
+ pubDate, err := dateparse.ParseLocal(pub)
+ if err != nil {
+ return false
+ }
+ return pubDate.AddDate(1, 0, 0).Before(time.Now())
+}
diff --git a/render.go b/render.go
index b1d9974..cd687a8 100644
--- a/render.go
+++ b/render.go
@@ -2,21 +2,16 @@ package main
import (
"bytes"
- "encoding/json"
"errors"
"fmt"
"html/template"
- "log"
"net/http"
- "net/url"
"os"
"path"
"path/filepath"
"strings"
- "time"
- gogeouri "git.jlel.se/jlelse/go-geouri"
- "github.com/araddon/dateparse"
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
servertiming "github.com/mitchellh/go-server-timing"
)
@@ -48,142 +43,32 @@ const (
func (a *goBlog) initRendering() error {
a.templates = map[string]*template.Template{}
templateFunctions := template.FuncMap{
- "menu": func(blog *configBlog, id string) *menu {
- return blog.Menus[id]
- },
- "user": func() *configUser {
- return a.cfg.User
- },
- "md": func(content string) template.HTML {
- htmlContent, err := a.renderMarkdown(content, false)
- if err != nil {
- log.Fatal(err)
- return ""
- }
- return template.HTML(htmlContent)
- },
- "html": func(s string) template.HTML {
- return template.HTML(s)
- },
+ "md": a.safeRenderMarkdownAsHTML,
+ "html": wrapStringAsHTML,
// Post specific
- "p": func(p *post, parameter string) string {
- return p.firstParameter(parameter)
- },
- "ps": func(p *post, parameter string) []string {
- return p.Parameters[parameter]
- },
- "hasp": func(p *post, parameter string) bool {
- return len(p.Parameters[parameter]) > 0
- },
- "title": func(p *post) string {
- return p.title()
- },
- "content": func(p *post) template.HTML {
- return a.html(p)
- },
- "summary": func(p *post) string {
- return a.summary(p)
- },
- "translations": func(p *post) []*post {
- return a.translations(p)
- },
- "shorturl": func(p *post) string {
- return a.shortPostURL(p)
- },
+ "p": firstPostParameter,
+ "ps": postParameter,
+ "hasp": postHasParameter,
+ "content": a.postHtml,
+ "summary": a.postSummary,
+ "translations": a.postTranslations,
+ "shorturl": a.shortPostURL,
// Others
"dateformat": dateFormat,
- "isodate": func(date string) string {
- return dateFormat(date, "2006-01-02")
- },
- "unixtodate": func(unix int64) string {
- return time.Unix(unix, 0).Local().String()
- },
- "now": func() string {
- return time.Now().Local().String()
- },
- "dateadd": func(date string, years, months, days int) string {
- d, err := dateparse.ParseLocal(date)
- if err != nil {
- return ""
- }
- return d.AddDate(years, months, days).Local().String()
- },
- "datebefore": func(date string, before string) bool {
- d, err := dateparse.ParseLocal(date)
- if err != nil {
- return false
- }
- b, err := dateparse.ParseLocal(before)
- if err != nil {
- return false
- }
- return d.Before(b)
- },
- "asset": a.assetFileName,
- "assetsri": a.assetSRI,
- "string": a.ts.GetTemplateStringVariantFunc(),
- "include": func(templateName string, data ...interface{}) (template.HTML, error) {
- if len(data) == 0 || len(data) > 2 {
- return "", errors.New("wrong argument count")
- }
- if rd, ok := data[0].(*renderData); ok {
- if len(data) == 2 {
- nrd := *rd
- nrd.Data = data[1]
- rd = &nrd
- }
- var buf bytes.Buffer
- err := a.templates[templateName].ExecuteTemplate(&buf, templateName, rd)
- return template.HTML(buf.String()), err
- }
- return "", errors.New("wrong arguments")
- },
- "urlize": urlize,
- "sort": sortedStrings,
- "absolute": func(path string) string {
- return a.getFullAddress(path)
- },
- "blogrelative": func(blog *configBlog, path string) string {
- return blog.getRelativePath(path)
- },
- "jsonFile": func(filename string) *map[string]interface{} {
- parsed := &map[string]interface{}{}
- content, err := os.ReadFile(filename)
- if err != nil {
- return nil
- }
- err = json.Unmarshal(content, parsed)
- if err != nil {
- fmt.Println(err.Error())
- return nil
- }
- return parsed
- },
- "mentions": func(absolute string) []*mention {
- mentions, _ := a.db.getWebmentions(&webmentionsRequestConfig{
- target: absolute,
- status: webmentionStatusApproved,
- asc: true,
- })
- return mentions
- },
- "urlToString": func(u url.URL) string {
- return u.String()
- },
- "geouri": func(u string) *gogeouri.Geo {
- g, _ := gogeouri.Parse(u)
- return g
- },
- "geourip": func(g *gogeouri.Geo, parameter string) (s string) {
- if gp := g.Parameters[parameter]; len(gp) > 0 {
- return gp[0]
- }
- return
- },
+ "isodate": isoDateFormat,
+ "unixtodate": unixToLocalDateString,
+ "now": localNowString,
+ "asset": a.assetFileName,
+ "string": a.ts.GetTemplateStringVariantFunc(),
+ "include": a.includeRenderedTemplate,
+ "urlize": urlize,
+ "sort": sortedStrings,
+ "absolute": a.getFullAddress,
+ "mentions": a.db.getWebmentionsByAddress,
"geotitle": a.db.geoTitle,
+ "geolink": geoOSMLink,
"opensearch": openSearchUrl,
}
-
baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt))
if err != nil {
return err
@@ -212,6 +97,7 @@ type renderData struct {
Canonical string
TorAddress string
Blog *configBlog
+ User *configUser
Data interface{}
LoggedIn bool
CommentsEnabled bool
@@ -223,6 +109,9 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
// Server timing
t := servertiming.FromContext(r.Context()).NewMetric("r").Start()
// Check render data
+ if data.User == nil {
+ data.User = a.cfg.User
+ }
if data.Blog == nil {
if len(data.BlogString) == 0 {
data.BlogString = a.cfg.DefaultBlog
@@ -256,7 +145,7 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
data.TorUsed = true
}
// Set content type
- w.Header().Set(contentType, contentTypeHTMLUTF8)
+ w.Header().Set(contentType, contenttype.HTMLUTF8)
// Minify and write response
var tw bytes.Buffer
err := a.templates[template].ExecuteTemplate(&tw, template, data)
@@ -264,7 +153,7 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- _, err = writeMinified(w, contentTypeHTML, tw.Bytes())
+ _, err = a.min.Write(w, contenttype.HTML, tw.Bytes())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -272,3 +161,20 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string,
// Server timing
t.Stop()
}
+
+func (a *goBlog) includeRenderedTemplate(templateName string, data ...interface{}) (template.HTML, error) {
+ if l := len(data); l < 1 || l > 2 {
+ return "", errors.New("wrong argument count")
+ }
+ if rd, ok := data[0].(*renderData); ok {
+ if len(data) == 2 {
+ nrd := *rd
+ nrd.Data = data[1]
+ rd = &nrd
+ }
+ var buf bytes.Buffer
+ err := a.templates[templateName].ExecuteTemplate(&buf, templateName, rd)
+ return template.HTML(buf.String()), err
+ }
+ return "", errors.New("wrong arguments")
+}
diff --git a/sessions.go b/sessions.go
index 32c7a27..fb9cb86 100644
--- a/sessions.go
+++ b/sessions.go
@@ -27,7 +27,7 @@ func (a *goBlog) initSessions() {
}
}
deleteExpiredSessions()
- hourlyHooks = append(hourlyHooks, deleteExpiredSessions)
+ a.hourlyHooks = append(a.hourlyHooks, deleteExpiredSessions)
a.loginSessions = &dbSessionStore{
codecs: securecookie.CodecsFromPairs(a.jwtKey()),
options: &sessions.Options{
diff --git a/telegram.go b/telegram.go
index 4299942..3d667db 100644
--- a/telegram.go
+++ b/telegram.go
@@ -16,7 +16,7 @@ const telegramBaseURL = "https://api.telegram.org/bot"
func (a *goBlog) initTelegram() {
a.pPostHooks = append(a.pPostHooks, func(p *post) {
if tg := a.cfg.Blogs[p.Blog].Telegram; tg.enabled() && p.isPublishedSectionPost() {
- if html := tg.generateHTML(p.title(), a.fullPostURL(p), a.shortPostURL(p)); html != "" {
+ if html := tg.generateHTML(p.Title(), a.fullPostURL(p), a.shortPostURL(p)); html != "" {
if err := tg.send(html, "HTML"); err != nil {
log.Printf("Failed to send post to Telegram: %v", err)
}
diff --git a/templateAssets.go b/templateAssets.go
index 4b406a7..c107282 100644
--- a/templateAssets.go
+++ b/templateAssets.go
@@ -2,23 +2,21 @@ package main
import (
"crypto/sha1"
- "crypto/sha512"
- "encoding/base64"
"fmt"
- "io"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"strings"
+
+ "git.jlel.se/jlelse/GoBlog/pkgs/contenttype"
)
const assetsFolder = "templates/assets"
type assetFile struct {
contentType string
- sri string
body []byte
}
@@ -50,7 +48,7 @@ func (a *goBlog) compileAsset(name string) (string, error) {
}
ext := path.Ext(name)
compiledExt := ext
- m := getMinifier()
+ m := a.min.Get()
switch ext {
case ".js":
content, err = m.Bytes("application/javascript", content)
@@ -67,18 +65,14 @@ func (a *goBlog) compileAsset(name string) (string, error) {
}
// Hashes
sha1Hash := sha1.New()
- sha512Hash := sha512.New()
- if _, err := io.MultiWriter(sha1Hash, sha512Hash).Write(content); err != nil {
+ if _, err := sha1Hash.Write(content); err != nil {
return "", err
}
// File name
compiledFileName := fmt.Sprintf("%x", sha1Hash.Sum(nil)) + compiledExt
- // SRI
- sriHash := fmt.Sprintf("sha512-%s", base64.StdEncoding.EncodeToString(sha512Hash.Sum(nil)))
// Create struct
a.assetFiles[compiledFileName] = &assetFile{
contentType: mime.TypeByExtension(compiledExt),
- sri: sriHash,
body: content,
}
return compiledFileName, err
@@ -89,10 +83,6 @@ func (a *goBlog) assetFileName(fileName string) string {
return "/" + a.assetFileNames[fileName]
}
-func (a *goBlog) assetSRI(fileName string) string {
- return a.assetFiles[a.assetFileNames[fileName]].sri
-}
-
func (a *goBlog) allAssetPaths() []string {
var paths []string
for _, name := range a.assetFileNames {
@@ -109,6 +99,6 @@ func (a *goBlog) serveAsset(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Cache-Control", "public,max-age=31536000,immutable")
- w.Header().Set(contentType, af.contentType+charsetUtf8Suffix)
+ w.Header().Set(contentType, af.contentType+contenttype.CharsetUtf8Suffix)
_, _ = w.Write(af.body)
}
diff --git a/templates/author.gohtml b/templates/author.gohtml
index 5f23fe7..722de35 100644
--- a/templates/author.gohtml
+++ b/templates/author.gohtml
@@ -1,5 +1,5 @@
{{ define "author" }}
- {{ with user }}
+ {{ with .User }}
{{ with .Picture }}
{{ end }}
{{ if .Name }}
diff --git a/templates/base.gohtml b/templates/base.gohtml
index 6c89dae..ec74f38 100644
--- a/templates/base.gohtml
+++ b/templates/base.gohtml
@@ -15,11 +15,7 @@
- {{ with user }}
- {{ range .Identities }}
-
- {{ end }}
- {{ end }}
+ {{ with .User }}{{ range .Identities }}
{{ end }}{{ end }}
{{ $os := opensearch .Blog }}
{{ if $os }}
diff --git a/templates/blogroll.gohtml b/templates/blogroll.gohtml
index 5f4793e..15e3f50 100644
--- a/templates/blogroll.gohtml
+++ b/templates/blogroll.gohtml
@@ -6,7 +6,7 @@
{{ with .Data.Title }}{{ . }}
{{ end }}
{{ with .Data.Description }}{{ md . }}{{ end }}
- {{ string .Blog.Lang "download" }}
+ {{ string .Blog.Lang "download" }}
{{ $lang := .Blog.Lang }}
{{ range .Data.Outlines }}
{{ $title := .Title }}
@@ -16,7 +16,7 @@
{{ range .Outlines }}
{{ $ct := .Title }}
{{ if not $ct }}{{ $ct = .Text }}{{ end }}
- {{ $ct }} ({{ string $lang "feed" }})
+ {{ $ct }} ({{ string $lang "feed" }})
{{ end }}
{{ end }}
diff --git a/templates/editor.gohtml b/templates/editor.gohtml
index e3ccd0f..aff3fd3 100644
--- a/templates/editor.gohtml
+++ b/templates/editor.gohtml
@@ -55,7 +55,7 @@ tags:
diff --git a/templates/footer.gohtml b/templates/footer.gohtml
index 0697025..ec685a2 100644
--- a/templates/footer.gohtml
+++ b/templates/footer.gohtml
@@ -1,13 +1,13 @@
{{ define "footer" }}