WIP: Node. New generation #1
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM golang:1.16 AS builder
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
COPY . /build
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
RUN go build -o /lessmore -v . && strip /lessmore
|
||||||
|
|
||||||
|
FROM alpine AS final
|
||||||
|
|
||||||
|
COPY --from=builder /lessmore /usr/local/bin/lessmore
|
||||||
|
COPY --from=builder /build/templates /usr/local/share/lessmore/templates
|
||||||
|
COPY --from=builder /build/static /usr/local/share/lessmore/static
|
||||||
|
|
||||||
|
USER nobody
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/lessmore"]
|
@ -1,14 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
|
|
||||||
# Usage:
|
|
||||||
# Required artifact from lessmore_builder in $(pwd)/out/
|
|
||||||
# Build:
|
|
||||||
# docker build -t lessmore -f Dockerfile.app .
|
|
||||||
# Run:
|
|
||||||
# docker run -ti -p 15582:15582 lessmore -listen 0.0.0.0:15582 -es http://$ES:9200 -esindex idec -estype idec
|
|
||||||
|
|
||||||
MAINTAINER Denis Zheleztsov <difrex.punk@gmail.com>
|
|
||||||
|
|
||||||
ADD out/lessmore /usr/bin
|
|
||||||
|
|
||||||
ENTRYPOINT ["lessmore"]
|
|
@ -1,25 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
|
|
||||||
MAINTAINER Denis Zheleztsov <difrex.punk@gmail.com>
|
|
||||||
|
|
||||||
# Usage:
|
|
||||||
# Build docker image:
|
|
||||||
# docker build -t lessmore_builder -f Dockerfile.builder .
|
|
||||||
# Build binary artifact:
|
|
||||||
# docker run -ti -v $(pwd)/out:/out/ lessmore_builder
|
|
||||||
|
|
||||||
# Install depends
|
|
||||||
RUN apk update && apk add git go
|
|
||||||
|
|
||||||
ENV GOPATH /usr
|
|
||||||
|
|
||||||
# Get go depends
|
|
||||||
RUN go get gitea.difrex.ru/Umbrella/fetcher
|
|
||||||
RUN go get gitea.difrex.ru/Umbrella/lessmore
|
|
||||||
RUN go install gitea.difrex.ru/Umbrella/lessmore
|
|
||||||
|
|
||||||
# Check build result
|
|
||||||
RUN echo -ne "Check build result\n==============="
|
|
||||||
RUN /usr/bin/lessmore --help || [[ $? -eq 2 ]]
|
|
||||||
|
|
||||||
ENTRYPOINT mv /usr/bin/lessmore /out/
|
|
22
main.go
22
main.go
@ -15,6 +15,12 @@ var (
|
|||||||
esMessagesType string
|
esMessagesType string
|
||||||
add string
|
add string
|
||||||
email string
|
email string
|
||||||
|
|
||||||
|
templatesDir string
|
||||||
|
|
||||||
|
serveStatic bool
|
||||||
|
staticDir string
|
||||||
|
|
||||||
debug bool
|
debug bool
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +32,11 @@ func init() {
|
|||||||
flag.StringVar(&esMessagesType, "estype", "", "ES index type")
|
flag.StringVar(&esMessagesType, "estype", "", "ES index type")
|
||||||
flag.StringVar(&add, "add", "", "User to add")
|
flag.StringVar(&add, "add", "", "User to add")
|
||||||
flag.StringVar(&email, "email", "", "User email address")
|
flag.StringVar(&email, "email", "", "User email address")
|
||||||
|
flag.StringVar(&templatesDir, "templates-dir", "/usr/local/share/lessmore/templates", "Path to templates dir")
|
||||||
|
|
||||||
|
flag.StringVar(&staticDir, "static-dir", "/usr/local/share/lessmore/static", "Path to static dir")
|
||||||
|
flag.BoolVar(&serveStatic, "static-serve", false, "Serve static files")
|
||||||
|
|
||||||
flag.BoolVar(&debug, "debug", false, "Debug output")
|
flag.BoolVar(&debug, "debug", false, "Debug output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -43,7 +54,16 @@ func main() {
|
|||||||
if add != "" {
|
if add != "" {
|
||||||
addUser(add, esconf)
|
addUser(add, esconf)
|
||||||
}
|
}
|
||||||
node.Serve(listen, esconf)
|
|
||||||
|
opts := &node.ServeOpts{
|
||||||
|
Listen: listen,
|
||||||
|
ES: esconf,
|
||||||
|
TemplatesDir: templatesDir,
|
||||||
|
ServeStatic: serveStatic,
|
||||||
|
StaticDir: staticDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Serve(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addUser(name string, esconf node.ESConf) {
|
func addUser(name string, esconf node.ESConf) {
|
||||||
|
38
node/api.go
38
node/api.go
@ -210,43 +210,55 @@ func (es ESConf) UPointHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write([]byte("msg ok"))
|
w.Write([]byte("msg ok"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServeOpts struct {
|
||||||
|
Listen string
|
||||||
|
TemplatesDir string
|
||||||
|
ServeStatic bool
|
||||||
|
StaticDir string
|
||||||
|
ES ESConf
|
||||||
|
}
|
||||||
|
|
||||||
// Serve ...
|
// Serve ...
|
||||||
func Serve(listen string, es ESConf) {
|
func Serve(opts *ServeOpts) {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/list.txt", es.ListTXTHandler).Methods("GET")
|
r.HandleFunc("/list.txt", opts.ES.ListTXTHandler).Methods("GET")
|
||||||
r.HandleFunc("/blacklist.txt", es.BlacklistTXT).Methods("GET")
|
r.HandleFunc("/blacklist.txt", opts.ES.BlacklistTXT).Methods("GET")
|
||||||
r.HandleFunc("/x/features", XFeaturesHandler).Methods("GET")
|
r.HandleFunc("/x/features", XFeaturesHandler).Methods("GET")
|
||||||
|
|
||||||
// Standart schemas
|
// Standart schemas
|
||||||
r.HandleFunc("/e/{echo}", es.EHandler).Methods("GET")
|
r.HandleFunc("/e/{echo}", opts.ES.EHandler).Methods("GET")
|
||||||
r.HandleFunc("/m/{msgid}", es.MHandler).Methods("GET")
|
r.HandleFunc("/m/{msgid}", opts.ES.MHandler).Methods("GET")
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
r.HandleFunc("/u/e/{echoes:[a-z0-9-_/.:]+}", es.UEHandler).Methods("GET")
|
r.HandleFunc("/u/e/{echoes:[a-z0-9-_/.:]+}", opts.ES.UEHandler).Methods("GET")
|
||||||
r.HandleFunc("/u/m/{ids:[a-zA-Z0-9-_/.:]+}", es.UMHandler).Methods("GET")
|
r.HandleFunc("/u/m/{ids:[a-zA-Z0-9-_/.:]+}", opts.ES.UMHandler).Methods("GET")
|
||||||
r.HandleFunc("/x/c/{echoes:[a-zA-Z0-9-_/.:]+}", es.XCHandler).Methods("GET")
|
r.HandleFunc("/x/c/{echoes:[a-zA-Z0-9-_/.:]+}", opts.ES.XCHandler).Methods("GET")
|
||||||
|
|
||||||
// Point methods
|
// Point methods
|
||||||
r.HandleFunc("/u/point", es.UPointHandler).Methods("POST")
|
r.HandleFunc("/u/point", opts.ES.UPointHandler).Methods("POST")
|
||||||
|
|
||||||
// Simple and clean SSR UI
|
// Simple and clean SSR UI
|
||||||
ssr := newSSR("./templates", es)
|
ssr := newSSR(opts.TemplatesDir, opts.ES)
|
||||||
r.HandleFunc("/", ssr.ssrRootHandler)
|
r.HandleFunc("/", ssr.ssrRootHandler)
|
||||||
r.HandleFunc("/forum", ssr.ssrForumHandler)
|
r.HandleFunc("/forum/page/{page:[0-9]+}", ssr.ssrForumHandler)
|
||||||
r.HandleFunc("/echo/{echo:[a-z0-9-_.]+}/page/{page:[0-9]+}", ssr.echoViewHandler)
|
r.HandleFunc("/echo/{echo:[a-z0-9-_.]+}/page/{page:[0-9]+}", ssr.echoViewHandler)
|
||||||
r.HandleFunc("/thread/{topicid:[a-z0-9-]+}", ssr.threadViewHandler)
|
r.HandleFunc("/thread/{topicid:[a-z0-9-]+}", ssr.threadViewHandler)
|
||||||
r.HandleFunc("/msg/{msgid:[a-zA-Z0-9]{20}}", ssr.singleMessageHandler)
|
r.HandleFunc("/msg/{msgid:[a-zA-Z0-9]{20}}", ssr.singleMessageHandler)
|
||||||
r.HandleFunc("/find", ssr.searchHandler).Methods(http.MethodGet)
|
r.HandleFunc("/find", ssr.searchHandler).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
if opts.ServeStatic {
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(opts.StaticDir))))
|
||||||
|
}
|
||||||
|
|
||||||
http.Handle("/", r)
|
http.Handle("/", r)
|
||||||
|
|
||||||
srv := http.Server{
|
srv := http.Server{
|
||||||
Handler: r,
|
Handler: r,
|
||||||
Addr: listen,
|
Addr: opts.Listen,
|
||||||
// WriteTimeout: 15 * time.Second,
|
// WriteTimeout: 15 * time.Second,
|
||||||
// ReadTimeout: 15 * time.Second,
|
// ReadTimeout: 15 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Print("Listening IDEC API on ", listen)
|
log.Print("Listening IDEC API on ", opts.Listen)
|
||||||
log.Fatal(srv.ListenAndServe())
|
log.Fatal(srv.ListenAndServe())
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package node
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -96,7 +97,7 @@ func (es ESConf) GetEchoMessageHashes(echo string) []string {
|
|||||||
searchQ := []byte(strings.Join([]string{
|
searchQ := []byte(strings.Join([]string{
|
||||||
`{"sort": [
|
`{"sort": [
|
||||||
{"date":{ "order": "desc" }},{ "_score":{ "order": "desc" }}],
|
{"date":{ "order": "desc" }},{ "_score":{ "order": "desc" }}],
|
||||||
"query": {"query_string" : {"fields": ["echo"], "query":"`, echo, `"}}, "size": 500}`}, ""))
|
"query": {"query_string" : {"fields": ["echo.keyword"], "query":"`, echo, `"}}, "size": 500}`}, ""))
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", searchURI, bytes.NewBuffer(searchQ))
|
req, err := http.NewRequest("POST", searchURI, bytes.NewBuffer(searchQ))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,7 +148,7 @@ func (es ESConf) GetLimitedEchoMessageHashes(echo string, offset int, limit int)
|
|||||||
searchQ := []byte(strings.Join([]string{
|
searchQ := []byte(strings.Join([]string{
|
||||||
`{"sort": [
|
`{"sort": [
|
||||||
{"date":{ "order": "`, order, `" }},{ "_score":{ "order": "`, order, `" }}],
|
{"date":{ "order": "`, order, `" }},{ "_score":{ "order": "`, order, `" }}],
|
||||||
"query": {"query_string" : {"fields": ["msgid", "echo"], "query":"`, echo, `"}}, "size":`, l, `}`}, ""))
|
"query": {"query_string" : {"fields": ["msgid.keyword", "echo.keyword"], "query":"`, echo, `"}}, "size":`, l, `}`}, ""))
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
|
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -229,7 +230,7 @@ func (es ESConf) GetUMMessages(msgs string) []string {
|
|||||||
{
|
{
|
||||||
"query": {
|
"query": {
|
||||||
"query_string" : {
|
"query_string" : {
|
||||||
"fields": ["msgid"],
|
"fields": ["msgid.keyword"],
|
||||||
"query":"` + strings.Join(messages, " OR ") + `"
|
"query":"` + strings.Join(messages, " OR ") + `"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -352,7 +353,7 @@ func (es ESConf) GetXC(echoes string) []string {
|
|||||||
{
|
{
|
||||||
"query": {
|
"query": {
|
||||||
"query_string" : {
|
"query_string" : {
|
||||||
"fields": ["echo"],
|
"fields": ["echo.keyword"],
|
||||||
"query": "` + strings.Join(strings.Split(echoes, "/"), " OR ") + `"
|
"query": "` + strings.Join(strings.Split(echoes, "/"), " OR ") + `"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -360,12 +361,12 @@ func (es ESConf) GetXC(echoes string) []string {
|
|||||||
"aggs": {
|
"aggs": {
|
||||||
"uniqueEcho": {
|
"uniqueEcho": {
|
||||||
"cardinality": {
|
"cardinality": {
|
||||||
"field": "echo"
|
"field": "echo.keyword"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"echo": {
|
"echo": {
|
||||||
"terms": {
|
"terms": {
|
||||||
"field": "echo",
|
"field": "echo.keyword",
|
||||||
"size": 1000
|
"size": 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,6 +421,7 @@ func (es ESConf) GetTopic(topicID string) (posts []i2es.ESDoc) {
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@ -455,6 +457,7 @@ func (es ESConf) GetMessage(msgID string) (posts []i2es.ESDoc) {
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@ -496,14 +499,17 @@ func (es ESConf) GetThreads(pageNum int, echoes ...string) (posts []i2es.ESDoc)
|
|||||||
}
|
}
|
||||||
log.Debug(rangeStr)
|
log.Debug(rangeStr)
|
||||||
|
|
||||||
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":{` + rangeStr + `}}},{"constant_score":{"filter":{"terms":{"echo.keyword": [` +
|
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", "repto"]}}}}}},"query":{"bool":{"must":[{"range":{"date":{` + rangeStr + `}}},{"constant_score":{"filter":{"terms":{"echo.keyword": [` +
|
||||||
strings.Join(ech, ",") +
|
strings.Join(ech, ",") +
|
||||||
`]}}}}]}}}`
|
`]}}}}]}}}`
|
||||||
|
log.Debug("Run: ", query)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewReader([]byte(query)))
|
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewReader([]byte(query)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
@ -520,6 +526,63 @@ func (es ESConf) GetThreads(pageNum int, echoes ...string) (posts []i2es.ESDoc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range data.Aggregations.Topics.Buckets {
|
for _, bucket := range data.Aggregations.Topics.Buckets {
|
||||||
|
// Empty topicid
|
||||||
|
if bucket.Key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, post := range bucket.Post.Hits.Hits {
|
||||||
|
posts = append(posts, post.Source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es ESConf) GetThreadsYear(pageNum int, echoes ...string) (posts []i2es.ESDoc) {
|
||||||
|
ech := defaultEchoes
|
||||||
|
if len(echoes) > 0 {
|
||||||
|
ech = []string{}
|
||||||
|
for _, echo := range echoes {
|
||||||
|
ech = append(ech, fmt.Sprintf(`"%s"`, echo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `{"sort":[{"date":{"order":"desc"}}],"aggs":{"topics":{"terms":{"field":"topicid.keyword","size":500},"aggs":{"post":{"top_hits":{"size":1,"sort":[{"date":{"order":"desc"}}],"_source":{"include": ["subg","author","date","echo","topicid","address", "repto"]}}}}}},"query":{"bool":{"must":[{"range":{"date":{"from": "now-365d", "to": "now"}}}, {"constant_score":{"filter":{"terms":{"echo.keyword": [` +
|
||||||
|
strings.Join(ech, ",") +
|
||||||
|
`]}}}}]}}}`
|
||||||
|
log.Debug("Run: ", query)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewReader([]byte(query)))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode > 200 {
|
||||||
|
d, _ := io.ReadAll(resp.Body)
|
||||||
|
log.Debug(string(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
var data ESAggsResp
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bucket := range data.Aggregations.Topics.Buckets {
|
||||||
|
// Empty topicid
|
||||||
|
if bucket.Key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for _, post := range bucket.Post.Hits.Hits {
|
for _, post := range bucket.Post.Hits.Hits {
|
||||||
posts = append(posts, post.Source)
|
posts = append(posts, post.Source)
|
||||||
}
|
}
|
||||||
@ -530,6 +593,8 @@ func (es ESConf) GetThreads(pageNum int, echoes ...string) (posts []i2es.ESDoc)
|
|||||||
|
|
||||||
func (es ESConf) GetLatestPosts(sum int) []i2es.ESDoc {
|
func (es ESConf) GetLatestPosts(sum int) []i2es.ESDoc {
|
||||||
query := fmt.Sprintf(`{"sort": [{"date": {"order": "desc"}}, {"_score": {"order": "desc" }}], "size": %d}`, sum)
|
query := fmt.Sprintf(`{"sort": [{"date": {"order": "desc"}}, {"_score": {"order": "desc" }}], "size": %d}`, sum)
|
||||||
|
log.Debugf("Do %s request", query)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer([]byte(query)))
|
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer([]byte(query)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
@ -583,18 +648,18 @@ func (es ESConf) GetEchoesList() []echo {
|
|||||||
"aggs": {
|
"aggs": {
|
||||||
"uniqueEcho": {
|
"uniqueEcho": {
|
||||||
"cardinality": {
|
"cardinality": {
|
||||||
"field": "echo"
|
"field": "echo.keyword"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"echo": {
|
"echo": {
|
||||||
"terms": {
|
"terms": {
|
||||||
"field": "echo",
|
"field": "echo.keyword",
|
||||||
"size": 1000
|
"size": 1000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`)
|
}`)
|
||||||
|
log.Debugf("Do %s request", searchQ)
|
||||||
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
|
req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
|
36
node/ssr.go
36
node/ssr.go
@ -29,6 +29,7 @@ type PageData struct {
|
|||||||
CurrentPage string
|
CurrentPage string
|
||||||
PageNum int
|
PageNum int
|
||||||
Posts []i2es.ESDoc
|
Posts []i2es.ESDoc
|
||||||
|
ShowPaginator bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PageData) GetDate(date string) string {
|
func (p *PageData) GetDate(date string) string {
|
||||||
@ -47,12 +48,13 @@ func (p *PageData) Dec() int {
|
|||||||
return p.PageNum - 1
|
return p.PageNum - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ssr) newPageData(page string, posts []i2es.ESDoc, num int) *PageData {
|
func (s *ssr) newPageData(page string, posts []i2es.ESDoc, num int, showPaginator bool) *PageData {
|
||||||
return &PageData{
|
return &PageData{
|
||||||
Echoes: s.es.GetEchoesList(),
|
Echoes: s.es.GetEchoesList(),
|
||||||
Posts: posts,
|
Posts: posts,
|
||||||
CurrentPage: page,
|
CurrentPage: page,
|
||||||
PageNum: num,
|
PageNum: num,
|
||||||
|
ShowPaginator: showPaginator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +65,7 @@ func (s *ssr) ssrRootHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData("feed", s.es.GetLatestPosts(50), 1)); err != nil {
|
if err := tpl.Execute(w, s.newPageData("feed", s.es.GetLatestPosts(50), 1, true)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +84,17 @@ func (s *ssr) ssrForumHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
num = getPageNum(mux.Vars(r)["page"])
|
num = getPageNum(mux.Vars(r)["page"])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData("forum", s.es.GetThreads(num), num)); err != nil {
|
bcVal := r.URL.Query().Get("year")
|
||||||
|
bc, _ := strconv.ParseBool(bcVal)
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
if err := tpl.Execute(w, s.newPageData("forum", s.es.GetThreadsYear(num), num, false)); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tpl.Execute(w, s.newPageData("forum", s.es.GetThreads(num), num, true)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +120,7 @@ func (s *ssr) threadViewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
thread = posts[0].Subg
|
thread = posts[0].Subg
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData(thread, posts, 1)); err != nil {
|
if err := tpl.Execute(w, s.newPageData(thread, posts, 1, true)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,7 +158,17 @@ func (s *ssr) echoViewHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
page = getPageNum(vars["page"])
|
page = getPageNum(vars["page"])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData(echo, s.es.GetThreads(page, echo), page)); err != nil {
|
bcVal := r.URL.Query().Get("year")
|
||||||
|
bc, _ := strconv.ParseBool(bcVal)
|
||||||
|
|
||||||
|
if bc {
|
||||||
|
if err := tpl.Execute(w, s.newPageData("forum", s.es.GetThreadsYear(page, echo), page, false)); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tpl.Execute(w, s.newPageData(echo, s.es.GetThreads(page, echo), page, true)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +188,7 @@ func (s *ssr) singleMessageHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData(msgid, s.es.GetMessage(msgid), 1)); err != nil {
|
if err := tpl.Execute(w, s.newPageData(msgid, s.es.GetMessage(msgid), 1, false)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +213,7 @@ func (s *ssr) searchHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
posts[i].Date = parseTime(posts[i].Date)
|
posts[i].Date = parseTime(posts[i].Date)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tpl.Execute(w, s.newPageData("search", posts, 1)); err != nil {
|
if err := tpl.Execute(w, s.newPageData("search", posts, 1, false)); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
static/css/bootstrap.min.css
vendored
Normal file
7
static/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
static/images/bg.webp
Normal file
BIN
static/images/bg.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 119 KiB |
BIN
static/images/favicon.png
Normal file
BIN
static/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -1,29 +1,32 @@
|
|||||||
{{ define "header" }}
|
{{ define "header" }}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<title>{{ if .CurrentPage }}static | {{ .CurrentPage }}{{ else }}static{{ end }}</title>
|
<title>{{ if .CurrentPage }}static | {{ .CurrentPage }}{{ else }}static{{ end }}</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="icon" type="image/png" href="/static/images/favicon.png">
|
||||||
|
</head>
|
||||||
<body class="dynamic-bg">
|
<body class="dynamic-bg">
|
||||||
|
|
||||||
<!-- Panel -->
|
<!-- Panel -->
|
||||||
<nav class="navbar navbar-expand-lg sticky-top navbar-dark dynamic-panel mb-2 shadow">
|
<nav class="navbar navbar-expand-sm sticky-top navbar-dark dynamic-panel mb-2 shadow lg">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">{{ if .CurrentPage }}static | {{ .CurrentPage }}{{ else }}static{{ end }}</a>
|
<span class="navbar-brand">{{ if .CurrentPage }}static | {{ .CurrentPage }}{{ else }}static{{ end }}</span>
|
||||||
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<a class="nav-link"
|
<a class="nav-link"
|
||||||
href="#"
|
href="/"
|
||||||
role="button">
|
role="button">
|
||||||
feed
|
feed
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<a class="nav-link"
|
<a class="nav-link"
|
||||||
href="/forum"
|
href="/forum/page/1"
|
||||||
role="button">
|
role="button">
|
||||||
forum
|
threads
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
@ -36,12 +39,12 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<form method="get" action="/find" class="d-flex">
|
<form method="get" action="/find" class="d-flex">
|
||||||
<input
|
<input
|
||||||
class="form-control me-2 bg-dark text-white-50 border-0"
|
class="form-control me-2 base2 border-0"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder='search query'
|
placeholder='search query'
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
name="query">
|
name="query">
|
||||||
<button class="btn btn-outline-light" type="submit">Search</button>
|
<button class="btn text-white-50" type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{ define "style" }}
|
{{ define "style" }}
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-image: url("https://dynamic.lessmore.pw/assets/bg.webp");
|
background-image: url("/static/images/bg.webp");
|
||||||
/* Full height */
|
/* Full height */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@ -13,7 +13,12 @@
|
|||||||
|
|
||||||
background-color: #002b36;
|
background-color: #002b36;
|
||||||
|
|
||||||
color: #657b83;
|
color: #eee8d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base2 {
|
||||||
|
background-color: #073642;
|
||||||
|
color: #eee8d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dynamic-echo {
|
.dynamic-echo {
|
||||||
@ -35,7 +40,7 @@
|
|||||||
|
|
||||||
.dynamic-post-text {
|
.dynamic-post-text {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
color: #839496;
|
color: #93a1a1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dynamic-opacity-1 {
|
.dynamic-opacity-1 {
|
||||||
@ -52,8 +57,34 @@
|
|||||||
box-shadow: 0px 0px 15px #002b36;
|
box-shadow: 0px 0px 15px #002b36;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.dynamic-post {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
background-color: #002b36;
|
||||||
|
box-shadow: 0px 0px 15px #002b36;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dynamic-post a {
|
.dynamic-post a {
|
||||||
color: #268bd2;
|
color: #268bd2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
color: #268bd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.echo {
|
||||||
|
}
|
||||||
|
|
||||||
|
.echo-card:hover {
|
||||||
|
background-color: #073642;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.hideit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -1,20 +1,35 @@
|
|||||||
{{ define "echoes" }}
|
{{ define "echoes" }}
|
||||||
|
|
||||||
<div class="echoList">
|
<!--
|
||||||
|
Echoes list component
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
{ template "echoes" . }
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="hideit echo">
|
||||||
{{ range .Echoes }}
|
{{ range .Echoes }}
|
||||||
|
|
||||||
<div class="card dynamic-echo dynamic-opacity-95 mb-1 mt-1">
|
<a
|
||||||
<div class="card-header text-info container-fluid">
|
style="text-decoration:none;"
|
||||||
<div class="row">
|
href="/echo/{{ .Name }}/page/1">
|
||||||
<div class="col-9">
|
<div class="card echo-card dynamic-echo dynamic-opacity-95 mb-1 mt-1 p-1 shadow">
|
||||||
<a class="text-white-50" href="/echo/{{ .Name }}/page/1">{{ .Name }}</a>
|
|
||||||
</div>
|
<div class="card-title">
|
||||||
<div class="col-3 text-end">
|
|
||||||
<span class="text-white-50">{{ .Docs }}</span>
|
<strong class="text-link">
|
||||||
</div>
|
{{ .Name }}
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<div class="card-subtitle text-white-50">
|
||||||
|
<span>{{ .Docs }} messages</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,36 +2,57 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row container-fluid">
|
<div class="row container-fluid">
|
||||||
<table class="table table-sm border-0 dynamic-bg dynamic-post-title dynamic-post dynamic-opacity-95 rounded">
|
<table class="table table-responsive-sm table-borderless dynamic-bg dynamic-post-title dynamic-post dynamic-opacity-95 rounded">
|
||||||
<caption class="dynamic-bg dynamic-opacity-95 rounded">
|
|
||||||
|
<caption class="dynamic-bg dynamic-opacity-95 rounded text-center">
|
||||||
|
{{ if .ShowPaginator }}
|
||||||
{{ $prevPage := (.Dec) }}
|
{{ $prevPage := (.Dec) }}
|
||||||
{{ $nextPage := (.Inc) }}
|
{{ $nextPage := (.Inc) }}
|
||||||
|
|
||||||
|
<a href="{{ $nextPage }}">prev</a>
|
||||||
|
|
||||||
{{ if (gt .PageNum 1) }}
|
{{ if (gt .PageNum 1) }}
|
||||||
<a href="{{ $prevPage }}">prev</a>
|
<a href="{{ $prevPage }}">next</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
<a href="{{ $nextPage }}">next</a>
|
{{ end }}
|
||||||
</caption>
|
</caption>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th scope="col">echo</th>
|
<th scope="col">echo</th>
|
||||||
<th scope="col">thread</th>
|
<th scope="col">thread</th>
|
||||||
<th scope="col">latest</th>
|
<th scope="col">latest</th>
|
||||||
<th scope="col">address</th>
|
<th class="hideit" scope="col">address</th>
|
||||||
<th scope="col">date</th>
|
<th class="hideit" scope="col">date</th>
|
||||||
<!-- <th scope="col">comments</th> -->
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ $page := . }}
|
{{ $page := . }}
|
||||||
{{ range .Posts }}
|
{{ range $i, $p := .Posts }}
|
||||||
|
{{ if (eq $i 0) }}
|
||||||
|
<div class="text-white-50">
|
||||||
|
{{ ($page.GetDate $p.Date) }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a class="text-white-50" href="/echo/{{ .Echo }}/page/1">{{ .Echo }}</a></td>
|
<td>
|
||||||
<td><strong><a href="/thread/{{ .TopicID }}">{{ .Subg }}</a></strong></td>
|
<a
|
||||||
<td>{{ .Author }}</td>
|
class="text-white-50"
|
||||||
<td>{{ .Address }}</td>
|
style="text-decoration:none;"
|
||||||
<td>{{ ($page.GetDate .Date) }}</td>
|
href="/echo/{{ .Echo }}/page/1">
|
||||||
|
{{ $p.Echo }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td><strong><a href="/thread/{{ .TopicID }}">{{ $p.Subg }}</a></strong></td>
|
||||||
|
<td><strong>{{ $p.Author }}</strong></td>
|
||||||
|
<td class="hideit">{{ $p.Address }}</td>
|
||||||
|
<td class="hideit">{{ ($page.GetDate $p.Date) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="?year=true">year</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,31 +4,13 @@
|
|||||||
|
|
||||||
<div class="container-fluid mt-5">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{ $prevPage := (.Dec) }}
|
|
||||||
{{ $nextPage := (.Inc) }}
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
{{ if (gt .PageNum 1) }}
|
|
||||||
<div class="col">
|
|
||||||
<div class="text-start">
|
|
||||||
<a href="/echo/pipe.2032/page/{{ $prevPage }}">prev</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<div class="col">
|
|
||||||
<div class="text-end">
|
|
||||||
<a href="/echo/pipe.2032/page/{{ $nextPage }}">next</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ template "forum" . }}
|
{{ template "forum" . }}
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
{{ template "style" }}
|
{{ template "style" }}
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
{{ template "forum" . }}
|
{{ template "forum" . }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
{{ template "style" }}
|
{{ template "style" }}
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
{{ range .Posts}}
|
{{ range .Posts}}
|
||||||
{{ template "post" . }}
|
{{ template "post" . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
<div class="container-fluid mt-5">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
{{ template "latest posts" . }}
|
{{ template "latest posts" . }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
<div class="container-fluid mt-5">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
{{ template "latest posts" . }}
|
{{ template "latest posts" . }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
{{ template "style" }}
|
{{ template "style" }}
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid mt-5">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2 hideit">
|
||||||
{{ template "echoes" . }}
|
{{ template "echoes" . }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-9 text-primary">
|
<div class="col-sm col-9 text-primary">
|
||||||
{{ template "latest posts" . }}
|
{{ template "latest posts" . }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user