diff --git a/go.sum b/go.sum index 3aede04..3ba0dcb 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b h1:K0vLl90b8 gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rcNfqAtzWqj1MsvxDuqTuqTNiJ7r6f1reQvsuUaiHYY= 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/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= diff --git a/node/api.go b/node/api.go index 5821929..acfdd2d 100644 --- a/node/api.go +++ b/node/api.go @@ -232,6 +232,7 @@ func Serve(listen string, es ESConf) { // Simple and clean SSR UI ssr := newSSR("./templates", es) r.HandleFunc("/", ssr.ssrRootHandler) + r.HandleFunc("/forum", ssr.ssrForumHandler) http.Handle("/", r) diff --git a/node/elastic.go b/node/elastic.go index 15aca14..936e3da 100644 --- a/node/elastic.go +++ b/node/elastic.go @@ -366,6 +366,42 @@ func (es ESConf) GetXC(echoes string) []string { return counts } +type ThreadBucket struct { + DocCount int64 `json:"doc_count"` + Key string `json:"key"` + Post Hits +} + +func (es ESConf) GetThreads(echoes ...string) (posts []i2es.ESDoc) { + query := `{"sort":[{"date":{"order":"desc"}}],"aggs":{"topics":{"terms":{"field":"topicid.keyword","size":100},"aggs":{"post":{"top_hits":{"size":1,"sort":[{"date":{"order":"desc"}}],"_source":{"include":["subg","author","date","echo","topicid","address"]}}}}}},"query":{"bool":{"must":[{"range":{"date":{"from":"now-30d","to":"now-0d"}}},{"constant_score":{"filter":{"terms":{"echo.keyword":["idec.talks","pipe.2032","linux.14","develop.16","dynamic.local","std.club","std.hugeping","oldpc.51t.ru","difrex.blog","ii.test.14"]}}}}]}}}` + req, err := http.NewRequest("POST", es.searchURI(), bytes.NewReader([]byte(query))) + if err != nil { + log.Error(err) + return + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Error(err) + return + } + defer resp.Body.Close() + + var data ESAggsResp + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + log.Error(err) + return + } + for _, bucket := range data.Aggregations.Topics.Buckets { + for _, post := range bucket.Post.Hits.Hits { + posts = append(posts, post.Source) + } + } + + return +} + func (es ESConf) GetLatestPosts(sum int) []i2es.ESDoc { query := fmt.Sprintf(`{"sort": [{"date": {"order": "desc"}}, {"_score": {"order": "desc" }}], "size": %d}`, sum) req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer([]byte(query))) diff --git a/node/go.sum b/node/go.sum index 22a2149..3aede04 100644 --- a/node/go.sum +++ b/node/go.sum @@ -3,7 +3,6 @@ gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rc 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/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -27,7 +26,6 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/node/ssr.go b/node/ssr.go index eea1dab..a2b1d4d 100644 --- a/node/ssr.go +++ b/node/ssr.go @@ -1,9 +1,7 @@ package node import ( - "html/template" "net/http" - "os" "gitea.difrex.ru/Umbrella/fetcher/i2es" log "github.com/sirupsen/logrus" @@ -21,47 +19,40 @@ func newSSR(templatesDir string, es ESConf) *ssr { } } -func (s *ssr) templatePath(name string) string { - return s.templatesDir + string(os.PathSeparator) + name +type PageData struct { + Echoes []echo + CurrentPage string + Posts []i2es.ESDoc } -func (s *ssr) getTemplate(name string) (*template.Template, error) { - t := template.New("root.html") - - tpl, err := t.ParseFiles( - s.templatePath(name+".html"), - s.templatePath("style.html"), - s.templatePath("echoes.html"), - s.templatePath("post.html"), - s.templatePath("latest_posts.html"), - s.templatePath("header.html"), - s.templatePath("footer.html"), - ) - if err != nil { - return nil, err +func (s *ssr) newPageData(page string, posts []i2es.ESDoc) *PageData { + return &PageData{ + Echoes: s.es.GetEchoesList(), + Posts: posts, + CurrentPage: page, } - - return tpl, nil } func (s *ssr) ssrRootHandler(w http.ResponseWriter, r *http.Request) { - LogRequest(r) - tpl, err := s.getTemplate("root") if err != nil { log.Error(err) return } - var root struct { - Echoes []echo - CurrentPage string - Posts []i2es.ESDoc - } - root.Echoes = s.es.GetEchoesList() - root.Posts = s.es.GetLatestPosts(100) - - if err := tpl.Execute(w, root); err != nil { + if err := tpl.Execute(w, s.newPageData("", s.es.GetLatestPosts(50))); err != nil { + log.Error(err) + } +} + +func (s *ssr) ssrForumHandler(w http.ResponseWriter, r *http.Request) { + tpl, err := s.getTemplate("forum") + if err != nil { + log.Error(err) + return + } + + if err := tpl.Execute(w, s.newPageData("", s.es.GetThreads())); err != nil { log.Error(err) } } diff --git a/node/structs.go b/node/structs.go index ebeaaf2..26c1288 100644 --- a/node/structs.go +++ b/node/structs.go @@ -39,6 +39,20 @@ type ESSearchResp struct { Hits `json:"hits"` } +type ESAggsResp struct { + ESSearchResp + Aggregations struct { + Topics struct { + Buckets []struct { + Bucket + Post struct { + Hits Hits `json:"hits"` + } `json:"post"` + } `json:"buckets"` + } `json:"topics"` + } `json:"aggregations"` +} + type Hits struct { Total int64 `json:"total"` MaxScore float64 `json:"max_score"` diff --git a/node/templates.go b/node/templates.go new file mode 100644 index 0000000..b6d13ad --- /dev/null +++ b/node/templates.go @@ -0,0 +1,98 @@ +package node + +import ( + "os" + "strings" + "text/template" + + log "github.com/sirupsen/logrus" +) + +const ( + // Common templates + CommonTplDir = "common" + + // Reusable components + ComponentsTplDir = "components" + + // Something like component of components + MetaTplDir = "meta" + + // Main pages + ViewsTplDir = "views" +) + +func (s *ssr) tplDir(name string) string { + return strings.Join([]string{s.templatesDir, name}, string(os.PathSeparator)) +} + +func (s *ssr) readTplDir(name string) ([]string, error) { + var paths []string + + dir, err := os.ReadDir(s.templatesDir + string(os.PathSeparator) + name) + if err != nil { + return nil, err + } + + for _, entry := range dir { + if strings.HasSuffix(entry.Name(), ".html") { + paths = append(paths, strings.Join( + []string{s.templatesDir, name, entry.Name()}, + string(os.PathSeparator))) + } + } + + return paths, nil +} + +func (s *ssr) tplsPaths() ([]string, error) { + var paths []string + // Read common dir + commonTpls, err := s.readTplDir(CommonTplDir) + if err != nil { + return nil, err + } + paths = append(paths, commonTpls...) + + // Read components dir + compTpls, err := s.readTplDir(ComponentsTplDir) + if err != nil { + return nil, err + } + paths = append(paths, compTpls...) + + // Read meta dir + metaTpls, err := s.readTplDir(MetaTplDir) + if err != nil { + return nil, err + } + paths = append(paths, metaTpls...) + + return paths, nil +} + +func (s *ssr) componentsForTemplate(name string) []string { + paths := []string{s.tplDir(ViewsTplDir) + string(os.PathSeparator) + name + ".html"} + tpls, err := s.tplsPaths() + if err != nil { + log.Error(err) + return paths + } + + return append(paths, tpls...) +} + +func (s *ssr) templatePath(name string) string { + return s.templatesDir + string(os.PathSeparator) + name +} + +func (s *ssr) getTemplate(name string) (*template.Template, error) { + t := template.New(name + ".html") + + tpl, err := t.ParseFiles(s.componentsForTemplate(name)...) + if err != nil { + return nil, err + } + + return tpl, nil +} diff --git a/templates/common/footer.html b/templates/common/footer.html new file mode 100644 index 0000000..98e820a --- /dev/null +++ b/templates/common/footer.html @@ -0,0 +1,12 @@ +{{ define "footer" }} + + +
+ copyleft 2021 difrex at lessmore dot pw; + + source code +
+