From a21f48c3099c5a6552b9821380e52d81c4cc9e64 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Mon, 10 May 2021 17:37:34 +0200 Subject: [PATCH] Persistent cache for blogroll and stats --- blogroll.go | 55 +++++++++++++++++++++++++++++-------------- blogstats.go | 50 ++++++++++++++++++++++++++------------- cache.go | 2 -- comments.go | 1 - config.go | 20 +++++++--------- databaseMigrations.go | 9 +++++++ go.mod | 7 +++--- go.sum | 18 +++++++------- http.go | 2 +- main.go | 1 + persistantCache.go | 28 ++++++++++++++++++++++ 11 files changed, 132 insertions(+), 61 deletions(-) create mode 100644 persistantCache.go diff --git a/blogroll.go b/blogroll.go index bb9fde4..edb4b8c 100644 --- a/blogroll.go +++ b/blogroll.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "io" "log" @@ -10,6 +11,7 @@ import ( "time" "github.com/kaorimatz/go-opml" + servertiming "github.com/mitchellh/go-server-timing" "github.com/thoas/go-funk" "golang.org/x/sync/singleflight" ) @@ -18,14 +20,11 @@ var blogrollCacheGroup singleflight.Group func serveBlogroll(w http.ResponseWriter, r *http.Request) { blog := r.Context().Value(blogContextKey).(string) - c := appConfig.Blogs[blog].Blogroll - if !c.Enabled { - serve404(w, r) - return - } + t := servertiming.FromContext(r.Context()).NewMetric("bg").Start() outlines, err, _ := blogrollCacheGroup.Do(blog, func() (interface{}, error) { - return getBlogrollOutlines(c) + return getBlogrollOutlines(blog) }) + t.Stop() if err != nil { log.Println("Failed to get outlines:", err.Error()) serveError(w, r, "", http.StatusInternalServerError) @@ -34,6 +33,7 @@ func serveBlogroll(w http.ResponseWriter, r *http.Request) { if appConfig.Cache != nil && appConfig.Cache.Enable { setInternalCacheExpirationHeader(w, r, int(appConfig.Cache.Expiration)) } + c := appConfig.Blogs[blog].Blogroll render(w, r, templateBlogroll, &renderData{ BlogString: blog, Data: map[string]interface{}{ @@ -47,13 +47,8 @@ func serveBlogroll(w http.ResponseWriter, r *http.Request) { func serveBlogrollExport(w http.ResponseWriter, r *http.Request) { blog := r.Context().Value(blogContextKey).(string) - c := appConfig.Blogs[blog].Blogroll - if !c.Enabled { - serve404(w, r) - return - } outlines, err, _ := blogrollCacheGroup.Do(blog, func() (interface{}, error) { - return getBlogrollOutlines(c) + return getBlogrollOutlines(blog) }) if err != nil { log.Println("Failed to get outlines:", err.Error()) @@ -75,10 +70,10 @@ func serveBlogrollExport(w http.ResponseWriter, r *http.Request) { }) } -func getBlogrollOutlines(config *configBlogroll) ([]*opml.Outline, error) { - if config.cachedOutlines != nil && time.Since(config.lastCache).Minutes() < 60 { - // return cache if younger than 60 min - return config.cachedOutlines, nil +func getBlogrollOutlines(blog string) ([]*opml.Outline, error) { + config := appConfig.Blogs[blog].Blogroll + if cache := loadOutlineCache(blog); cache != nil { + return cache, nil } req, err := http.NewRequest(http.MethodGet, config.Opml, nil) if err != nil { @@ -117,11 +112,35 @@ func getBlogrollOutlines(config *configBlogroll) ([]*opml.Outline, error) { } else { outlines = sortOutlines(outlines) } - config.cachedOutlines = outlines - config.lastCache = time.Now() + cacheOutlines(blog, outlines) return outlines, nil } +func cacheOutlines(blog string, outlines []*opml.Outline) { + var opmlBuffer bytes.Buffer + _ = opml.Render(&opmlBuffer, &opml.OPML{ + Version: "2.0", + DateCreated: time.Now().UTC(), + Outlines: outlines, + }) + cachePersistently("blogroll_"+blog, opmlBuffer.Bytes()) +} + +func loadOutlineCache(blog string) []*opml.Outline { + data, err := retrievePersistentCache("blogroll_" + blog) + if err != nil || data == nil { + return nil + } + o, err := opml.NewParser(bytes.NewReader(data)).Parse() + if err != nil { + return nil + } + if time.Since(o.DateCreated).Minutes() > 60 { + return nil + } + return o.Outlines +} + func sortOutlines(outlines []*opml.Outline) []*opml.Outline { sort.Slice(outlines, func(i, j int) bool { name1 := outlines[i].Title diff --git a/blogstats.go b/blogstats.go index 20664d9..de5ccd6 100644 --- a/blogstats.go +++ b/blogstats.go @@ -2,12 +2,22 @@ package main import ( "database/sql" + "encoding/json" + "log" "net/http" - "sync" "golang.org/x/sync/singleflight" ) +func initBlogStats() { + f := func(p *post) { + resetBlogStats(p.Blog) + } + postHooks[postPostHook] = append(postHooks[postPostHook], f) + postHooks[postUpdateHook] = append(postHooks[postPostHook], f) + postHooks[postDeleteHook] = append(postHooks[postPostHook], f) +} + func serveBlogStats(w http.ResponseWriter, r *http.Request) { blog := r.Context().Value(blogContextKey).(string) canonical := blogPath(blog) + appConfig.Blogs[blog].BlogStats.Path @@ -21,8 +31,6 @@ func serveBlogStats(w http.ResponseWriter, r *http.Request) { } var blogStatsCacheGroup singleflight.Group -var blogStatsCache = map[string]map[string]interface{}{} -var blogStatsCacheMutex sync.RWMutex func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) { blog := r.Context().Value(blogContextKey).(string) @@ -41,12 +49,9 @@ func serveBlogStatsTable(w http.ResponseWriter, r *http.Request) { } func getBlogStats(blog string) (data map[string]interface{}, err error) { - blogStatsCacheMutex.RLock() - if data, ok := blogStatsCache[blog]; ok && data != nil { - blogStatsCacheMutex.RUnlock() - return data, nil + if stats := loadBlogStatsCache(blog); stats != nil { + return stats, nil } - blogStatsCacheMutex.RUnlock() // Build query prq := &postsRequestConfig{ blog: blog, @@ -109,20 +114,33 @@ func getBlogStats(blog string) (data map[string]interface{}, err error) { } } } - blogStatsCacheMutex.Lock() - blogStatsCache[blog] = map[string]interface{}{ + data = map[string]interface{}{ "total": total, "years": years, "withoutdate": noDate, "months": months, } - data = blogStatsCache[blog] - blogStatsCacheMutex.Unlock() + cacheBlogStats(blog, data) return data, nil } -func clearBlogStatsCache() { - blogStatsCacheMutex.Lock() - blogStatsCache = map[string]map[string]interface{}{} - blogStatsCacheMutex.Unlock() +func cacheBlogStats(blog string, stats map[string]interface{}) { + jb, _ := json.Marshal(stats) + cachePersistently("blogstats_"+blog, jb) +} + +func loadBlogStatsCache(blog string) (stats map[string]interface{}) { + data, err := retrievePersistentCache("blogstats_" + blog) + if err != nil || data == nil { + return nil + } + err = json.Unmarshal(data, &stats) + if err != nil { + log.Println(err) + } + return stats +} + +func resetBlogStats(blog string) { + clearPersistentCache("blogstats_" + blog) } diff --git a/cache.go b/cache.go index 63e5f25..5fba71f 100644 --- a/cache.go +++ b/cache.go @@ -212,8 +212,6 @@ func getCache(key string, next http.Handler, r *http.Request) (item *cacheItem) func purgeCache() { cacheR.Clear() - // Clear blog stats as well - clearBlogStatsCache() } func setInternalCacheExpirationHeader(w http.ResponseWriter, r *http.Request, expiration int) { diff --git a/comments.go b/comments.go index cda07bf..3ea0196 100644 --- a/comments.go +++ b/comments.go @@ -39,7 +39,6 @@ func serveComment(w http.ResponseWriter, r *http.Request) { serveError(w, r, err.Error(), http.StatusInternalServerError) return } - w.Header().Set("X-Robots-Tag", "noindex") blog := r.Context().Value(blogContextKey).(string) render(w, r, templateComment, &renderData{ BlogString: blog, diff --git a/config.go b/config.go index 44c3f2f..2340035 100644 --- a/config.go +++ b/config.go @@ -4,9 +4,7 @@ import ( "errors" "net/url" "strings" - "time" - "github.com/kaorimatz/go-opml" "github.com/spf13/viper" ) @@ -119,16 +117,14 @@ type blogStats struct { } type configBlogroll struct { - Enabled bool `mapstructure:"enabled"` - Path string `mapstructure:"path"` - Opml string `mapstructure:"opml"` - AuthHeader string `mapstructure:"authHeader"` - AuthValue string `mapstructure:"authValue"` - Categories []string `mapstructure:"categories"` - Title string `mapstructure:"title"` - Description string `mapstructure:"description"` - cachedOutlines []*opml.Outline - lastCache time.Time + Enabled bool `mapstructure:"enabled"` + Path string `mapstructure:"path"` + Opml string `mapstructure:"opml"` + AuthHeader string `mapstructure:"authHeader"` + AuthValue string `mapstructure:"authValue"` + Categories []string `mapstructure:"categories"` + Title string `mapstructure:"title"` + Description string `mapstructure:"description"` } type customPage struct { diff --git a/databaseMigrations.go b/databaseMigrations.go index 5445a7c..1071b28 100644 --- a/databaseMigrations.go +++ b/databaseMigrations.go @@ -139,6 +139,15 @@ func migrateDb() error { return err }, }, + &migrator.Migration{ + Name: "00012", + Func: func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table persistent_cache (key text primary key, data blob, date text not null); + `) + return err + }, + }, ), ) if err != nil { diff --git a/go.mod b/go.mod index 411dced..f3742da 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/caddyserver/certmagic v0.13.1 github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f - github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88 + github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/elnormous/contenttype v1.0.0 github.com/felixge/httpsnoop v1.0.2 // indirect @@ -19,6 +19,7 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gofrs/flock v0.8.0 // indirect github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect + github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect github.com/google/go-cmp v0.5.4 // indirect github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe // indirect github.com/gorilla/feeds v1.1.1 @@ -59,9 +60,9 @@ require ( golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect golang.org/x/mod v0.4.1 // indirect - golang.org/x/net v0.0.0-20210508051633-16afe75a6701 + golang.org/x/net v0.0.0-20210510120150-4163338589ed golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 // indirect + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.62.0 // indirect diff --git a/go.sum b/go.sum index e194aa0..dbb2ed4 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= @@ -45,6 +44,8 @@ github.com/caddyserver/certmagic v0.13.1 h1:A5qLxh9J6/CYWEOHaj135IWAjCY0193ONxEy github.com/caddyserver/certmagic v0.13.1/go.mod h1:+zhQtEgLOyXRA/KRduHXNhGGdTeqRM4ePj8eBGD/2CQ= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -60,8 +61,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M= github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY= -github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88 h1:a13o656FuUv6beRVDMZqN6D/Cw8SjL02TT3yuV4UeTo= -github.com/dgraph-io/ristretto v0.0.4-0.20210311064603-e4f298c8aa88/go.mod h1:MIonLggsKgZLUSt414ExgwNtlOL5MuEoAJP514mwGe8= +github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3 h1:jU/wpYsEL+8JPLf/QcjkQKI5g0dOjSuwcMjkThxt5x0= +github.com/dgraph-io/ristretto v0.0.4-0.20210504190834-0bf2acd73aa3/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= @@ -104,6 +105,8 @@ github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hz github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg= github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= +github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -294,7 +297,6 @@ github.com/snabb/diagio v1.0.0/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1Q github.com/snabb/sitemap v1.0.0 h1:7vJeNPAaaj7fQSRS3WYuJHzUjdnhLdSLLpvVtnhbzC0= github.com/snabb/sitemap v1.0.0/go.mod h1:Id8uz1+WYdiNmSjEi4BIvL5UwNPYLsTHzRbjmDwNDzA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -418,8 +420,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210508051633-16afe75a6701 h1:lQVgcB3+FoAXOb20Dp6zTzAIrpj1k/yOOBN7s+Zv1rA= -golang.org/x/net v0.0.0-20210508051633-16afe75a6701/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -456,8 +458,8 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo= -golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/http.go b/http.go index ca0e694..9677792 100644 --- a/http.go +++ b/http.go @@ -226,7 +226,7 @@ func buildStaticHandlersRouters() error { commentsRouter = chi.NewRouter() commentsRouter.Use(privateModeHandler...) - commentsRouter.With(cacheMiddleware).Get("/{id:[0-9]+}", serveComment) + commentsRouter.With(cacheMiddleware, noIndexHeader).Get("/{id:[0-9]+}", serveComment) commentsRouter.With(captchaMiddleware).Post("/", createComment) commentsRouter.Group(func(r chi.Router) { // Admin diff --git a/main.go b/main.go index 0c4ab29..c93bd5f 100644 --- a/main.go +++ b/main.go @@ -135,6 +135,7 @@ func main() { return } initTelegram() + initBlogStats() // Start cron hooks startHourlyHooks() diff --git a/persistantCache.go b/persistantCache.go new file mode 100644 index 0000000..c8415be --- /dev/null +++ b/persistantCache.go @@ -0,0 +1,28 @@ +package main + +import ( + "database/sql" + "time" +) + +func cachePersistently(key string, data []byte) error { + date, _ := toLocal(time.Now().String()) + _, err := appDbExec("insert or replace into persistent_cache(key, data, date) values(@key, @data, @date)", sql.Named("key", key), sql.Named("data", data), sql.Named("date", date)) + return err +} + +func retrievePersistentCache(key string) (data []byte, err error) { + if row, err := appDbQueryRow("select data from persistent_cache where key = @key", sql.Named("key", key)); err == sql.ErrNoRows { + return nil, nil + } else if err != nil { + return nil, err + } else { + row.Scan(&data) + return data, err + } +} + +func clearPersistentCache(pattern string) error { + _, err := appDbExec("delete from persistent_cache where key like @pattern", sql.Named("pattern", pattern)) + return err +}