From cd34d0953a91e04369382b515a22b383bfed24f3 Mon Sep 17 00:00:00 2001 From: pluja Date: Tue, 20 Feb 2024 19:20:44 +0100 Subject: [PATCH] big speed improvements and remove redis dependency --- .dockerignore | 3 +- .gitignore | 3 +- blogo/articles.go | 111 ++++++++------------------- blogo/badger.go | 184 +++++++++++++++++++++++++++++++++++++++++++++ blogo/go.mod | 13 ++++ blogo/go.sum | 88 +++++++++++++++++++++- blogo/handlers.go | 95 ++++++----------------- blogo/main.go | 12 ++- blogo/redis.go | 59 --------------- blogo/routes.go | 2 +- blogo/rss.go | 26 +++---- blogo/statics.go | 53 +++++++++++++ blogo/watcher.go | 9 ++- docker-compose.yml | 18 +---- 14 files changed, 427 insertions(+), 249 deletions(-) create mode 100644 blogo/badger.go delete mode 100644 blogo/redis.go create mode 100644 blogo/statics.go diff --git a/.dockerignore b/.dockerignore index dc81eaf..d6316b5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ node_modules *.yml articles -Dockerfile \ No newline at end of file +Dockerfile +content \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2ff4abb..5582c8c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ go.work .env articles TODO.md -style.css \ No newline at end of file +style.css +content \ No newline at end of file diff --git a/blogo/articles.go b/blogo/articles.go index 50c8b6e..6237f71 100644 --- a/blogo/articles.go +++ b/blogo/articles.go @@ -2,19 +2,18 @@ package main import ( "bytes" - "context" "encoding/json" "errors" "fmt" "html/template" "os" + "path" "path/filepath" "strconv" "strings" "time" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/redis/go-redis/v9" "github.com/rs/zerolog/log" "github.com/yuin/goldmark" highlighting "github.com/yuin/goldmark-highlighting/v2" @@ -59,13 +58,24 @@ func InitGoldmark() { func LoadArticles() error { InitGoldmark() var slugs []string - err := filepath.Walk(fmt.Sprintf("%v/articles/", os.Getenv("CONTENT_PATH")), func(fpath string, info os.FileInfo, err error) error { + err := filepath.Walk(path.Join(os.Getenv("CONTENT_PATH"), "/articles/"), func(fpath string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && strings.HasSuffix(info.Name(), ".md") { - err := LoadArticle(fpath) + article, err := GetArticleFromFile(fpath) + if err != nil { + log.Error().Msgf("Could not get article from file %v", fpath) + return err + } + + err = LoadArticle(article) + if err != nil { + return err + } + + err = GenerateArticleStatic(article) if err != nil { return err } @@ -79,14 +89,15 @@ func LoadArticles() error { } // Remove articles that are no longer in the articles folder from Redis - ctx := context.Background() - articleSlugs, err := RedisDb.SMembers(ctx, "articles").Result() + articleSlugs, err := Badger.GetAllArticleSlugs() if err != nil { log.Err(err).Msg("Error getting articles from Redis") articleSlugs = []string{} } left, _ := Difference(articleSlugs, slugs) - log.Printf("Removing %v articles from Redis. Left articles: %v", left, slugs) + if len(left) > 0 { + log.Printf("Removing %v articles from Badger. Left articles: %v", len(left), slugs) + } for _, articleSlug := range left { if !StringInSlice(articleSlug, slugs) { RemoveArticle(fmt.Sprintf("%v/articles/%v.md", os.Getenv("CONTENT_PATH"), articleSlug)) @@ -121,15 +132,8 @@ func GetArticleContent(filename string) (template.HTML, string, error) { } // Loads an article from a markdown file and stores it in Redis -func LoadArticle(filepath string) (err error) { - slug, extension := ParseFilePath(filepath) - filename := fmt.Sprintf("%v%v", slug, extension) - - article, err := GetArticleFromFile(filename) - if err != nil { - return err - } - switch slug { +func LoadArticle(article ArticleData) (err error) { + switch article.Slug { case "about": About.Slug = "about" About.Data = article @@ -139,19 +143,7 @@ func LoadArticle(filepath string) (err error) { if err != nil { return fmt.Errorf("error while marshalling article to JSON: %v", err) } - - // Store the article in Redis with the slug as the key - ctx := context.Background() - err = RedisDb.Set(ctx, slug, articleJson, 0).Err() - if err != nil { - return fmt.Errorf("error while setting article to Redis: %v", err) - } - RedisDb.SAdd(ctx, "articles", slug) - - // Add article to a set for each tag - for _, tag := range article.Tags { - RedisDb.SAdd(ctx, fmt.Sprintf("tag:%v", tag), slug) - } + Badger.Set("post_"+article.Slug, articleJson) } if article.NostrUrl == "" && article.Slug != "about" { @@ -168,59 +160,13 @@ func LoadArticle(filepath string) (err error) { func RemoveArticle(filename string) { log.Printf("Removing article: %v", filename) slug, _ := ParseFilePath(filename) - - ctx := context.Background() - // Get the article from Redis - article, err := RedisDb.Get(ctx, slug).Result() - if err != nil || err == redis.Nil { - log.Err(err).Msgf("Could not get article %v from Redis", slug) - return - } - - // Unmarshal the article data - var articleData ArticleData - err = json.Unmarshal([]byte(article), &articleData) - if err != nil { - log.Err(err).Msgf("Could not unmarshal article %v from Redis", slug) - return - } - - pipe := RedisDb.Pipeline() - - // Delete the article - delCmd := pipe.Del(ctx, slug) - - // Remove from the "articles" set - sRemCmd := pipe.SRem(ctx, "articles", slug) - - // Remove from each tag set - tagRemCmds := make([]*redis.IntCmd, len(articleData.Tags)) - for i, tag := range articleData.Tags { - tagRemCmds[i] = pipe.SRem(ctx, fmt.Sprintf("tag:%v", tag), slug) - } - - _, err = pipe.Exec(ctx) - if err != nil { - log.Err(err).Msgf("Could not execute pipelined commands for article %v", slug) - return - } - - // Check for command errors - if delCmd.Err() != nil || sRemCmd.Err() != nil { - log.Err(err).Msg("Failed to delete article or remove from 'articles' set") - } - for _, cmd := range tagRemCmds { - if cmd.Err() != nil { - log.Err(cmd.Err()).Msg("Failed to remove article from a tag set") - } - } + Badger.DeleteArticle(slug) } // Returns an ArticleData struct from a markdown file -func GetArticleFromFile(filename string) (ArticleData, error) { - log.Printf("Getting article from file: %v", filename) - filepath := fmt.Sprintf("%v/articles/%v", os.Getenv("CONTENT_PATH"), filename) - slug, _ := ParseFilePath(filepath) +func GetArticleFromFile(filepath string) (ArticleData, error) { + slug, extension := ParseFilePath(filepath) + filename := slug + extension var article ArticleData // Read the markdown file @@ -362,7 +308,12 @@ func AddMetadataToFile(filename, key, value string) error { return err } - LoadArticle(filename) + article, err := GetArticleFromFile(path.Join(os.Getenv("CONTENT_PATH"), "/articles", filename)) + if err != nil { + log.Error().Msgf("Could not get article from file %v", filePath) + return err + } + LoadArticle(article) return nil } diff --git a/blogo/badger.go b/blogo/badger.go new file mode 100644 index 0000000..fdd4e69 --- /dev/null +++ b/blogo/badger.go @@ -0,0 +1,184 @@ +package main + +import ( + "encoding/json" + "strings" + + "github.com/rs/zerolog/log" + + badger "github.com/dgraph-io/badger/v4" +) + +var Badger Database + +type Database struct { + *badger.DB +} + +func InitBadger() { + opts := badger.DefaultOptions("").WithInMemory(true) + opts.Logger = nil // Disable logging + var err error + Badger.DB, err = badger.Open(opts) + if err != nil { + log.Fatal().Err(err) + } +} + +// BLOGO SPECIFIC FUNCTIONS + +func (d *Database) GetAllArticleSlugs() ([]string, error) { + var keys []string + err := d.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.Prefix = []byte("post_") + it := txn.NewIterator(opts) + defer it.Close() + + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + key := item.Key() + keys = append(keys, strings.Trim(string(key), "post_")) + } + return nil + }) + return keys, err +} + +func (d *Database) GetPostBySlug(slug string) (ArticleData, error) { + var value []byte + var aData ArticleData + err := d.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte("post_" + slug)) + if err != nil { + return err + } + + err = item.Value(func(val []byte) error { + value = val + return nil + }) + return err + }) + if err != nil { + return aData, err + } + + err = json.Unmarshal(value, &aData) + if err != nil { + return aData, err + } + return aData, err +} + +func (d *Database) GetAllArticles() []ArticleData { + var articles []ArticleData + articleBytes := d.GetValuesWithPrefix("post_") + for _, ab := range articleBytes { + var article ArticleData + err := json.Unmarshal(ab, &article) + if err != nil { + log.Error().Err(err).Msg("Error unmarshalling article from Badger:") + continue + } + articles = append(articles, article) + } + return articles +} + +func (d *Database) DeleteArticle(key string) error { + return d.Delete("post_" + key) +} + +// GENERIC FUNCTIONS + +func (d *Database) Set(key string, value []byte) error { + return d.Update(func(txn *badger.Txn) error { + return txn.Set([]byte(key), value) + }) +} + +func (d *Database) Get(key string) ([]byte, error) { + var value []byte + err := d.View(func(txn *badger.Txn) error { + item, err := txn.Get([]byte(key)) + if err != nil { + return err + } + + err = item.Value(func(val []byte) error { + value = val + return nil + }) + return err + }) + return value, err +} + +func (d *Database) Delete(key string) error { + return d.Update(func(txn *badger.Txn) error { + return txn.Delete([]byte(key)) + }) +} + +func (d *Database) GetAllKeys() ([]string, error) { + var keys []string + err := d.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + it := txn.NewIterator(opts) + defer it.Close() + + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + key := item.Key() + keys = append(keys, string(key)) + } + return nil + }) + return keys, err +} + +func (d *Database) GetValues() ([][]byte, error) { + var values [][]byte + err := d.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + it := txn.NewIterator(opts) + defer it.Close() + + for it.Rewind(); it.ValidForPrefix(opts.Prefix); it.Next() { + item := it.Item() + err := item.Value(func(val []byte) error { + values = append(values, val) + return nil + }) + if err != nil { + return err + } + } + return nil + }) + return values, err +} + +func (d *Database) GetValuesWithPrefix(prefix string) [][]byte { + var values [][]byte + d.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.Prefix = []byte(prefix) + it := txn.NewIterator(opts) + defer it.Close() + + for it.Rewind(); it.ValidForPrefix(opts.Prefix); it.Next() { + item := it.Item() + err := item.Value(func(val []byte) error { + values = append(values, val) + return nil + }) + if err != nil { + return err + } + } + return nil + }) + return values +} diff --git a/blogo/go.mod b/blogo/go.mod index 3a1d045..26df2e6 100644 --- a/blogo/go.mod +++ b/blogo/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/alecthomas/chroma/v2 v2.2.0 + github.com/dgraph-io/badger/v4 v4.2.0 github.com/dustin/go-humanize v1.0.1 github.com/fsnotify/fsnotify v1.6.0 github.com/go-chi/chi v1.5.4 @@ -29,25 +30,37 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/ws v1.2.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v1.12.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.12.3 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/puzpuzpuz/xsync v1.5.2 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + go.opencensus.io v0.22.5 // indirect golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.6.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gorm.io/driver/mysql v1.4.7 // indirect gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 // indirect ) diff --git a/blogo/go.sum b/blogo/go.sum index 5fc9643..6220b77 100644 --- a/blogo/go.sum +++ b/blogo/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= @@ -28,8 +30,10 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -41,11 +45,18 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -67,19 +78,37 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.2.0 h1:u0p9s3xLYpZCA1z5JgCkMeB34CKCMMQbM+G8Ii7YD0I= github.com/gobwas/ws v1.2.0/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -98,7 +127,11 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -123,6 +156,7 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -138,6 +172,7 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -147,6 +182,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU= github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -154,31 +191,58 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw= golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -186,19 +250,38 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -215,3 +298,4 @@ gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/blogo/handlers.go b/blogo/handlers.go index ed6478c..1b21b50 100644 --- a/blogo/handlers.go +++ b/blogo/handlers.go @@ -1,9 +1,10 @@ package main import ( - "context" - "encoding/json" + "fmt" "net/http" + "os" + "path" "sort" "github.com/go-chi/chi/v5" @@ -11,7 +12,12 @@ import ( ) func GetIndex(w http.ResponseWriter, r *http.Request) { - articles := GetAllArticles() + articles := Badger.GetAllArticles() + // Sort by date + sort.Slice(articles, func(i, j int) bool { + return articles[i].Date.After(articles[j].Date) + }) + varmap := map[string]interface{}{ "Articles": articles, "Blogo": Blogo, @@ -23,60 +29,28 @@ func GetIndex(w http.ResponseWriter, r *http.Request) { } } -func GetBlogPost(w http.ResponseWriter, r *http.Request) { +func ServeBlogPost(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") + log.Debug().Msgf("%v", slug) + blogPath := fmt.Sprintf("%v/content", os.Getenv("CONTENT_PATH")) + filePath := path.Join(blogPath, fmt.Sprintf("%s.html", slug)) - // Get article from redis - ctx := context.Background() - result, err := RedisDb.Get(ctx, slug).Result() - if err != nil { - log.Err(err).Msg("Error getting article from Redis") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Unmarshal the result into an Article struct - var article ArticleData - err = json.Unmarshal([]byte(result), &article) - if err != nil { - log.Err(err).Msg("Error unmarshalling article from Redis") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - varmap := map[string]interface{}{ - "Article": article, - "Blogo": Blogo, - } + log.Debug().Msgf("%v", filePath) - // Execute the template from templates.go - if err := PostTmpl.ExecuteTemplate(w, "base", varmap); err != nil { - log.Error().Err(err).Msg("Error executing template:") - http.Error(w, err.Error(), http.StatusInternalServerError) - } + http.ServeFile(w, r, filePath) } func GetRawMarkdown(w http.ResponseWriter, r *http.Request) { slug := chi.URLParam(r, "slug") // Get article from redis - ctx := context.Background() - result, err := RedisDb.Get(ctx, slug).Result() + article, err := Badger.GetPostBySlug(slug) if err != nil { log.Err(err).Msg("Error getting article from Redis") http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Unmarshal the result into an Article struct - var article ArticleData - err = json.Unmarshal([]byte(result), &article) - if err != nil { - log.Err(err).Msg("Error unmarshalling article from Redis") - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "text/plain") w.Write([]byte(article.Md)) } @@ -85,42 +59,21 @@ func GetTagPosts(w http.ResponseWriter, r *http.Request) { tag := chi.URLParam(r, "tag") // Get all article IDs from the corresponding tag set - ctx := context.Background() - articleIDs, err := RedisDb.SMembers(ctx, "tag:"+tag).Result() - if err != nil { - log.Err(err).Msg("Error getting articles from Redis") - articleIDs = []string{} - } - - var articles []ArticleData - // Iterate over each article ID and fetch the article detail - for _, id := range articleIDs { - result, err := RedisDb.Get(ctx, id).Result() - if err != nil { - log.Err(err).Msg("Error getting article from Redis") - continue - } - - // Unmarshal the result into an Article struct - var article ArticleData - err = json.Unmarshal([]byte(result), &article) - if err != nil { - log.Err(err).Msg("Error unmarshalling article from Redis") - } - - // Skip draft articles - if !article.Draft { - articles = append(articles, article) + articles := Badger.GetAllArticles() + var tagArticles []ArticleData + for _, article := range articles { + if StringInSlice(tag, article.Tags) { + tagArticles = append(tagArticles, article) } } // Sort by date - sort.Slice(articles, func(i, j int) bool { - return articles[i].Date.After(articles[j].Date) + sort.Slice(tagArticles, func(i, j int) bool { + return tagArticles[i].Date.After(tagArticles[j].Date) }) varmap := map[string]interface{}{ - "Articles": articles, + "Articles": tagArticles, "Blogo": Blogo, "Tag": tag, } diff --git a/blogo/main.go b/blogo/main.go index f3734c2..ce3fb6e 100644 --- a/blogo/main.go +++ b/blogo/main.go @@ -36,7 +36,14 @@ func main() { if *dev { os.Setenv("DEV", "true") - os.Setenv("CONTENT_PATH", "..") + //os.Setenv("CONTENT_PATH", "..") + os.Setenv("DEV", "true") + log.Logger = log.Output( + zerolog.ConsoleWriter{ + Out: os.Stdout, + TimeFormat: "15:04:05", + }, + ).With().Caller().Logger() zerolog.SetGlobalLevel(zerolog.DebugLevel) } @@ -67,7 +74,8 @@ func main() { } InitSettings() - InitRedis() + InitBadger() + //InitRedis() InitTemplates() r := InitRoutes() diff --git a/blogo/redis.go b/blogo/redis.go deleted file mode 100644 index f39c389..0000000 --- a/blogo/redis.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "fmt" - "os" - "sort" - - "github.com/redis/go-redis/v9" - "github.com/rs/zerolog/log" -) - -var RedisDb *redis.Client - -func InitRedis() { - raddr := fmt.Sprintf("%v:%v", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")) - RedisDb = redis.NewClient(&redis.Options{ - Addr: raddr, - Password: "", // no password set - DB: 0, // use default DB - }) -} - -func GetAllArticles() []ArticleData { - ctx := context.Background() - // Get all article IDs from the "articles" set - articleIDs, err := RedisDb.SMembers(ctx, "articles").Result() - if err != nil { - log.Err(err).Msg("Error getting articles from Redis") - articleIDs = []string{} - } - - var articles []ArticleData - // Iterate over each article ID and fetch the article detail - for _, id := range articleIDs { - result, err := RedisDb.Get(ctx, id).Result() - if err != nil { - log.Err(err).Msg("Error getting article from Redis") - continue - } - - // Unmarshal the result into an Article struct - var article ArticleData - err = json.Unmarshal([]byte(result), &article) - if err != nil { - log.Err(err).Msg("Error unmarshalling article from Redis") - } - - articles = append(articles, article) - } - - // Sort by date - sort.Slice(articles, func(i, j int) bool { - return articles[i].Date.After(articles[j].Date) - }) - - return articles -} diff --git a/blogo/routes.go b/blogo/routes.go index 73234d9..d5dfa10 100644 --- a/blogo/routes.go +++ b/blogo/routes.go @@ -54,7 +54,7 @@ func InitRoutes() *chi.Mux { r.Handle("/static/*", http.StripPrefix("/static/", fileServer)) r.Get("/", GetIndex) - r.Get("/p/{slug}", GetBlogPost) + r.Get("/p/{slug}", ServeBlogPost) r.Get("/p/{slug}/raw", GetRawMarkdown) r.Get("/t/{tag}", GetTagPosts) r.Get("/about", GetAbout) diff --git a/blogo/rss.go b/blogo/rss.go index 1cf10c1..9d7081f 100644 --- a/blogo/rss.go +++ b/blogo/rss.go @@ -1,10 +1,10 @@ package main import ( - "context" "encoding/json" "fmt" "os" + "sort" "time" "github.com/gorilla/feeds" @@ -21,7 +21,11 @@ func UpdateFeed() error { Created: now, } - articles := GetAllArticles() + articles := Badger.GetAllArticles() + // Sort by date + sort.Slice(articles, func(i, j int) bool { + return articles[i].Date.After(articles[j].Date) + }) feed.Items = []*feeds.Item{} for _, article := range articles { @@ -35,36 +39,30 @@ func UpdateFeed() error { feed.Items = append(feed.Items, item) } - // Save feed to Redis - ctx := context.Background() + // Save feed to badger json, err := json.Marshal(feed) if err != nil { log.Err(err).Msg("Error marshalling feed to JSON") return err } - err = RedisDb.Set(ctx, "feed", json, 0).Err() - if err != nil { - log.Err(err).Msg("Error saving feed to Redis") - } + Badger.Set("feed", json) return err } func GetFeed() feeds.Feed { - ctx := context.Background() - result, err := RedisDb.Get(ctx, "feed").Result() + result, err := Badger.Get("feed") if err != nil { - log.Err(err).Msg("Error getting feed from Redis") + log.Err(err).Msg("Error getting feed from Badger") return feeds.Feed{} } // Unmarshal the result into an Article struct var feed feeds.Feed - err = json.Unmarshal([]byte(result), &feed) + err = json.Unmarshal(result, &feed) if err != nil { - log.Err(err).Msg("Error unmarshalling feed from Redis") + log.Err(err).Msg("Error unmarshalling feed from Badger") return feeds.Feed{} } - return feed } diff --git a/blogo/statics.go b/blogo/statics.go new file mode 100644 index 0000000..b3fa73c --- /dev/null +++ b/blogo/statics.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "os" + "path" +) + +// Loads an article from a markdown file and stores it in Redis +func GenerateArticleStatic(article ArticleData) (err error) { + varmap := map[string]interface{}{ + "Article": article, + "Blogo": Blogo, + } + switch article.Slug { + case "about": + About.Slug = "about" + About.Data = article + default: + blogPath := fmt.Sprintf("%v/content", os.Getenv("CONTENT_PATH")) + _, err := os.Stat(blogPath) + + if os.IsNotExist(err) { + if err := os.Mkdir(blogPath, os.ModePerm); err != nil { + return fmt.Errorf("error creating blog directory: %v", err) + } + } else if err != nil { + return fmt.Errorf("error checking blog directory: %v", err) + } + + filePath := fmt.Sprintf("%v/%v.html", blogPath, article.Slug) + file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("error creating static HTML file: %v", err) + } + + err = PostTmpl.ExecuteTemplate(file, "base", varmap) + if err != nil { + return fmt.Errorf("error writing static HTML: %v", err) + } + + } + return nil +} + +func RemoveArticleStatic(filepath string) (err error) { + slug, _ := ParseFilePath(filepath) + filename := fmt.Sprintf("%v.html", slug) + + blogPath := fmt.Sprintf("%v/content", os.Getenv("CONTENT_PATH")) + + return os.Remove(path.Join(blogPath, filename)) +} diff --git a/blogo/watcher.go b/blogo/watcher.go index 3eced5d..a7ccfda 100644 --- a/blogo/watcher.go +++ b/blogo/watcher.go @@ -27,7 +27,11 @@ func InitWatcher() { if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { if strings.HasSuffix(event.Name, ".md") { log.Printf("Reloading article: %v", event.Name) - LoadArticle(event.Name) + article, _ := GetArticleFromFile(event.Name) + if article.Slug != "" { + LoadArticle(article) + GenerateArticleStatic(article) + } UpdateFeed() } } @@ -37,6 +41,7 @@ func InitWatcher() { if strings.HasSuffix(event.Name, ".md") { log.Printf("Removing article: %v", event.Name) RemoveArticle(event.Name) + RemoveArticleStatic(event.Name) UpdateFeed() } } @@ -46,7 +51,7 @@ func InitWatcher() { if strings.HasSuffix(event.Name, ".md") { log.Printf("Replacing article: %v", event.Name) RemoveArticle(event.Name) - LoadArticle(event.Name) + RemoveArticleStatic(event.Name) UpdateFeed() } } diff --git a/docker-compose.yml b/docker-compose.yml index 6168835..73b2439 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.9" services: blogo: @@ -6,21 +6,7 @@ services: container_name: blogo restart: unless-stopped env_file: .env - environment: - - REDIS_HOST=redis - - REDIS_PORT=6379 volumes: - ./articles:/app/articles ports: - - "3000:3000" - - redis: - image: redis:alpine - container_name: blogo-redis - restart: unless-stopped - volumes: - - redis:/data - -volumes: - redis: - name: blogo-redis \ No newline at end of file + - "127.0.0.1:3000:3000"