aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile22
-rw-r--r--cmd/shorg/serve/serve.go52
-rw-r--r--config/dev.yaml8
-rw-r--r--config/prod.yaml10
-rw-r--r--contrib/dev/docker-compose.yml (renamed from docker/docker-compose.yml)0
-rw-r--r--contrib/prod/.env3
-rw-r--r--contrib/prod/docker-compose.yml37
-rw-r--r--go.mod42
-rw-r--r--go.sum462
-rw-r--r--migrations/20241007210540_2_rating.go33
-rw-r--r--pkg/config/config.go10
-rw-r--r--pkg/handler/add/add.go (renamed from pkg/handler/add.go)4
-rw-r--r--pkg/handler/add/handler.go21
-rw-r--r--pkg/handler/admin/admin.go (renamed from pkg/handler/admin.go)36
-rw-r--r--pkg/handler/captcha/handler.go17
-rw-r--r--pkg/handler/feed/feed.go73
-rw-r--r--pkg/handler/feed/handler.go21
-rw-r--r--pkg/handler/handler.go14
-rw-r--r--pkg/handler/quote/handler.go26
-rw-r--r--pkg/handler/quote/index.go (renamed from pkg/handler/index.go)10
-rw-r--r--pkg/handler/quote/quote.go (renamed from pkg/handler/quote.go)4
-rw-r--r--pkg/handler/quote/random.go18
-rw-r--r--pkg/handler/quote/top.go28
-rw-r--r--pkg/handler/random.go31
-rw-r--r--pkg/handler/rate/handler.go19
-rw-r--r--pkg/handler/rate/rate.go69
-rw-r--r--pkg/middleware/context.go21
-rw-r--r--pkg/middleware/session/store.go230
-rw-r--r--pkg/model/quote.go1
-rw-r--r--pkg/tpl/add.templ24
-rw-r--r--pkg/tpl/add_templ.go52
-rw-r--r--pkg/tpl/layout.templ8
-rw-r--r--pkg/tpl/layout_templ.go10
-rw-r--r--pkg/tpl/list.templ (renamed from pkg/tpl/index.templ)10
-rw-r--r--pkg/tpl/list_templ.go (renamed from pkg/tpl/index_templ.go)15
-rw-r--r--pkg/tpl/quote.templ17
-rw-r--r--pkg/tpl/quote_templ.go48
-rw-r--r--pkg/tpl/random.templ27
-rw-r--r--pkg/tpl/random_templ.go37
-rw-r--r--pkg/tpl/rate.templ31
-rw-r--r--pkg/tpl/rate_templ.go85
-rw-r--r--static/css/style.css11
42 files changed, 1090 insertions, 607 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0ca7344
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+.PHONY: dev-infra-up
+dev-infra-up:
+ docker compose -f ./contrib/dev/docker-compose.yml up -d
+.PHONY: dev-infra-stop
+dev-infra-stop:
+ docker compose -f ./contrib/dev/docker-compose.yml stop
+.PHONY: dev-infra-down
+dev-infra-down:
+ docker compose -f ./contrib/dev/docker-compose.yml down
+.PHONY: generate
+generate:
+ go generate ./...
+ templ generate
+.PHONY: deploy
+deploy: generate
+ docker build -t gitrepo.ru/neonxp/shorg:latest .
+ docker push gitrepo.ru/neonxp/shorg:latest
+ docker context use curie
+ docker compose -f ./contrib/prod/docker-compose.yml --project-name shorg pull app
+ docker compose -f ./contrib/prod/docker-compose.yml --project-name shorg up --force-recreate --build -d
+ docker image prune -f
+ docker context use default
diff --git a/cmd/shorg/serve/serve.go b/cmd/shorg/serve/serve.go
index 73a7c68..04b6ad5 100644
--- a/cmd/shorg/serve/serve.go
+++ b/cmd/shorg/serve/serve.go
@@ -1,14 +1,22 @@
package serve
import (
+ echosession "github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
- "github.com/labstack/echo/v4/middleware"
- "github.com/ssoda/captcha"
+ echomiddleware "github.com/labstack/echo/v4/middleware"
"github.com/uptrace/bun/extra/bundebug"
"github.com/urfave/cli/v2"
"sh.org.ru/pkg/config"
"sh.org.ru/pkg/db"
"sh.org.ru/pkg/handler"
+ "sh.org.ru/pkg/handler/add"
+ "sh.org.ru/pkg/handler/admin"
+ "sh.org.ru/pkg/handler/captcha"
+ "sh.org.ru/pkg/handler/feed"
+ "sh.org.ru/pkg/handler/quote"
+ "sh.org.ru/pkg/handler/rate"
+ "sh.org.ru/pkg/middleware"
+ "sh.org.ru/pkg/middleware/session"
"sh.org.ru/static"
)
@@ -21,34 +29,30 @@ func Run(c *cli.Context) error {
db := db.New(cfg.DB)
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(cfg.Debug)))
- h := handler.Handler{DB: db}
+ store, err := session.New(db, cfg.Keypairs)
+ if err != nil {
+ return err
+ }
e := echo.New()
-
e.HTTPErrorHandler = handler.ErrorHandler
e.Use(
- middleware.Recover(),
- middleware.Logger(),
- middleware.RemoveTrailingSlash(),
+ echomiddleware.Recover(),
+ echomiddleware.Logger(),
+ echomiddleware.RemoveTrailingSlash(),
+ echosession.Middleware(store),
+ middleware.Context("config", cfg),
)
- e.GET("/", h.Index)
- e.GET("/quote/:id", h.Quote)
- e.GET("/random", h.Random)
- e.GET("/add", h.AddQuote)
- e.POST("/add", h.AddQuotePost)
- e.GET("/add/success", h.AddQuoteSuccess)
- e.GET("/captcha/*", echo.WrapHandler(captcha.Server(400, 65)))
-
- adminMW := middleware.BasicAuth(func(u, p string, ctx echo.Context) (bool, error) {
- return cfg.Admins[u] == p, nil
- })
-
- func(g *echo.Group) {
- g.GET("/", h.Admin)
- g.POST("/action", h.AdminAction)
- g.GET("/export", h.AdminExport)
- }(e.Group("/admin", adminMW))
+ router := handler.Router{
+ "/": quote.NewHandler(db),
+ "/add": add.NewHandler(db),
+ "/admin": admin.NewHandler(db, cfg),
+ "/captcha": captcha.NewHandler(),
+ "/rate": rate.NewHandler(db),
+ "/feed": feed.NewHandler(db, cfg),
+ }
+ router.Register(e)
e.StaticFS("/", static.FS)
diff --git a/config/dev.yaml b/config/dev.yaml
index 95acdeb..9d399e1 100644
--- a/config/dev.yaml
+++ b/config/dev.yaml
@@ -6,4 +6,10 @@ db:
listen: :8000
admins:
- admin: password \ No newline at end of file
+ admin: password
+
+host: https://sh.org.ru
+
+keys:
+ - 12345678901234567890123456789012
+ - 12345678901234567890123456789012 \ No newline at end of file
diff --git a/config/prod.yaml b/config/prod.yaml
index 9f8aa71..cd178cc 100644
--- a/config/prod.yaml
+++ b/config/prod.yaml
@@ -1,9 +1,15 @@
debug: true
db:
- dsn: "postgres://postgres:@localhost:5432/test?sslmode=disable"
+ dsn: "postgres://shorg:shorg@db:5432/test?sslmode=disable"
listen: :8000
admins:
- admin: password \ No newline at end of file
+ admin: password
+
+host: https://sh.org.ru
+
+keys:
+ - 12345678901234567890123456789012
+ - 12345678901234567890123456789012 \ No newline at end of file
diff --git a/docker/docker-compose.yml b/contrib/dev/docker-compose.yml
index 92b3871..92b3871 100644
--- a/docker/docker-compose.yml
+++ b/contrib/dev/docker-compose.yml
diff --git a/contrib/prod/.env b/contrib/prod/.env
new file mode 100644
index 0000000..4f0dd77
--- /dev/null
+++ b/contrib/prod/.env
@@ -0,0 +1,3 @@
+POSTGRES_DB=shorg
+POSTGRES_USER=shorg
+POSTGRES_PASSWORD=shorg \ No newline at end of file
diff --git a/contrib/prod/docker-compose.yml b/contrib/prod/docker-compose.yml
new file mode 100644
index 0000000..167301b
--- /dev/null
+++ b/contrib/prod/docker-compose.yml
@@ -0,0 +1,37 @@
+version: '3.3'
+
+services:
+ db:
+ image: postgres
+ shm_size: 128mb
+ env_file: .env
+ volumes:
+ - data:/var/lib/postgresql/data
+ healthcheck:
+ test: [ "CMD-SHELL", "pg_isready -U shorg -d shorg" ]
+ interval: 30s
+ timeout: 30s
+ retries: 5
+ restart: unless-stopped
+ app:
+ image: gitrepo.ru/neonxp/shorg:latest
+ volumes:
+ - config:/config
+ command: serve -config=/config/prod.yaml
+ depends_on:
+ db:
+ condition: service_healthy
+ ports:
+ - 8093:8000
+ restart: unless-stopped
+ migration:
+ image: gitrepo.ru/neonxp/shorg:latest
+ volumes:
+ - config:/config
+ command: db migrate -config=/config/prod.yaml
+ depends_on:
+ db:
+ condition: service_healthy
+volumes:
+ data:
+ config:
diff --git a/go.mod b/go.mod
index f31823c..79f2f68 100644
--- a/go.mod
+++ b/go.mod
@@ -3,67 +3,45 @@ module sh.org.ru
go 1.23.1
require (
- github.com/go-bun/bun-starter-kit v0.0.0-20221117143002-e3e263102887
+ github.com/a-h/templ v0.2.778
+ github.com/gorilla/securecookie v1.1.2
+ github.com/gorilla/sessions v1.4.0
+ github.com/labstack/echo-contrib v0.17.1
+ github.com/ssoda/captcha v1.0.0
github.com/uptrace/bun v1.2.3
+ github.com/uptrace/bun/extra/bundebug v1.2.3
github.com/urfave/cli/v2 v2.27.4
+ gopkg.in/yaml.v3 v3.0.1
)
require (
- github.com/a-h/templ v0.2.778 // indirect
- github.com/cespare/xxhash/v2 v2.2.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
- github.com/google/uuid v1.3.0 // indirect
- github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+ github.com/gorilla/context v1.1.2 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-sqlite3 v1.14.9 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
- github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
- github.com/ssoda/captcha v1.0.0 // indirect
- github.com/uptrace/bun/dialect/sqlitedialect v1.0.20 // indirect
- github.com/uptrace/bun/driver/sqliteshim v1.0.20 // indirect
- github.com/uptrace/bun/extra/bundebug v1.2.3 // indirect
- github.com/uptrace/bunrouter v1.0.9 // indirect
- github.com/uptrace/bunrouter/extra/bunrouterotel v1.0.9 // indirect
- github.com/uptrace/bunrouter/extra/reqlog v1.0.9 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
- go.opentelemetry.io/otel v1.1.0 // indirect
- go.opentelemetry.io/otel/trace v1.1.0 // indirect
- go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
golang.org/x/crypto v0.26.0 // indirect
- golang.org/x/mod v0.20.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
- golang.org/x/tools v0.24.0 // indirect
- golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
- lukechampine.com/uint128 v1.1.1 // indirect
mellium.im/sasl v0.3.1 // indirect
- modernc.org/cc/v3 v3.35.19 // indirect
- modernc.org/ccgo/v3 v3.12.95 // indirect
- modernc.org/libc v1.11.104 // indirect
- modernc.org/mathutil v1.4.1 // indirect
- modernc.org/memory v1.0.5 // indirect
- modernc.org/opt v0.1.1 // indirect
- modernc.org/sqlite v1.14.3 // indirect
- modernc.org/strutil v1.1.1 // indirect
- modernc.org/token v1.0.0 // indirect
)
require (
+ github.com/gorilla/feeds v1.2.0
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/labstack/echo/v4 v4.12.0
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
- github.com/uptrace/bun/dbfixture v1.2.3
github.com/uptrace/bun/dialect/pgdialect v1.2.3
github.com/uptrace/bun/driver/pgdriver v1.2.3
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
diff --git a/go.sum b/go.sum
index 1508a09..a005d45 100644
--- a/go.sum
+++ b/go.sum
@@ -1,514 +1,100 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-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/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
+github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
+github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
+github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
-github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
-github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
-github.com/go-bun/bun-starter-kit v0.0.0-20221117143002-e3e263102887 h1:cBT27By4hxKyYU+Wxpu7Ny7h7OCRvM3u1swn5+u6loY=
-github.com/go-bun/bun-starter-kit v0.0.0-20221117143002-e3e263102887/go.mod h1:Ib259v3Z/EtazXfkRXHM7qyCUj5G3FGfX38i7red1Wo=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-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.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
+github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
+github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
+github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
+github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
+github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
+github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
+github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
-github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU=
+github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
-github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
-github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
-github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
-github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/ssoda/captcha v1.0.0 h1:Xml0+JeNn8YnTzQTX5fZgRAgEl1L35y3DR59aoudmvg=
github.com/ssoda/captcha v1.0.0/go.mod h1:JjUYCzbREViqGScdUknPqmaoMIlWkLoOoDdKwXWNdKA=
-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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
-github.com/uptrace/bun v1.0.20/go.mod h1:Uv7z0z+7dXnUS9P5hMF0hdiM/4M+xOUHQCrZpyDrpRc=
github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0=
github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk=
-github.com/uptrace/bun/dbfixture v1.0.20/go.mod h1:oAZHy0q1WyoZvdXbSnlaD/E+sKkCAA1Cm5m6nDtE6z4=
-github.com/uptrace/bun/dbfixture v1.2.3 h1:yCsVQOYKSopE5bl2Fmr6i23Dd8H6GqdqvaWfVLiJ6bo=
-github.com/uptrace/bun/dbfixture v1.2.3/go.mod h1:QoeUj4s1pPatNKfz+wDry+6RTTOpfM1OJz+BRIzWpTo=
github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA=
github.com/uptrace/bun/dialect/pgdialect v1.2.3/go.mod h1:Vx9TscyEq1iN4tnirn6yYGwEflz0KG3rBZTBCLpKAjc=
-github.com/uptrace/bun/dialect/sqlitedialect v1.0.20 h1:V1vfu9TpuJ/YXjFU3046SHWSNIYGPNnVo4EXzt+y20U=
-github.com/uptrace/bun/dialect/sqlitedialect v1.0.20/go.mod h1:o46E4Pz+DKqFBxWwaNpPHTniF7X33sz2xySo/OvkHfM=
github.com/uptrace/bun/driver/pgdriver v1.2.3 h1:VA5TKB0XW7EtreQq2R8Qu/vCAUX2ECaprxGKI9iDuDE=
github.com/uptrace/bun/driver/pgdriver v1.2.3/go.mod h1:yDiYTZYd4FfXFtV01m4I/RkI33IGj9N254jLStaeJLs=
-github.com/uptrace/bun/driver/sqliteshim v1.0.20 h1:45SfRQVeRbpbdPhgGJieWI7oq+yNoEgRLBc6A+f2RZ8=
-github.com/uptrace/bun/driver/sqliteshim v1.0.20/go.mod h1:ghKfodChpoICMMFViEKhc+RYb3yMXCtnd3KWBdd3wvw=
-github.com/uptrace/bun/extra/bundebug v1.0.20 h1:lwuGUMiqujR3NuGDKgJu1j7XL3LsULSv1MDFHlYBAGs=
-github.com/uptrace/bun/extra/bundebug v1.0.20/go.mod h1:tDoi/zmjHkumthaCujwfI2+mni0G41HfJD4HC2oMdpk=
github.com/uptrace/bun/extra/bundebug v1.2.3 h1:2QBykz9/u4SkN9dnraImDcbrMk2fUhuq2gL6hkh9qSc=
github.com/uptrace/bun/extra/bundebug v1.2.3/go.mod h1:bihsYJxXxWZXwc1R3qALTHvp+npE0ElgaCvcjzyPPdw=
-github.com/uptrace/bunrouter v1.0.9 h1:AFkgNSX3ZnrVUNlNNwCo7IlpcrLOtEwhi/r8SuNZBC0=
-github.com/uptrace/bunrouter v1.0.9/go.mod h1:TwT7Bc0ztF2Z2q/ZzMuSVkcb/Ig/d3MQeP2cxn3e1hI=
-github.com/uptrace/bunrouter/extra/bunrouterotel v1.0.9 h1:JzQLTKR5zB+1YskI0ZmjV/RYnAjKgWCTS0pNbTOQOGk=
-github.com/uptrace/bunrouter/extra/bunrouterotel v1.0.9/go.mod h1:BSzjjLv/56BLPwn9WGurq5hwGyZAfaw8Kah5nnFZM2k=
-github.com/uptrace/bunrouter/extra/reqlog v1.0.9 h1:0kqqRrXZ1mKMg+Cu21xNpaEGvVcqrE9KkgXxYpTbYEc=
-github.com/uptrace/bunrouter/extra/reqlog v1.0.9/go.mod h1:n4POt1xb0YOb0HojwtBQDPhbloXvwZq1rF2vddGGloE=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/otel v1.1.0 h1:8p0uMLcyyIx0KHNTgO8o3CW8A1aA+dJZJW6PvnMz0Wc=
-go.opentelemetry.io/otel v1.1.0/go.mod h1:7cww0OW51jQ8IaZChIEdqLwgh+44+7uiTdWsAL0wQpA=
-go.opentelemetry.io/otel/trace v1.1.0 h1:N25T9qCL0+7IpOT8RrRy0WYlL7y6U0WiUJzXcVdXY/o=
-go.opentelemetry.io/otel/trace v1.1.0/go.mod h1:i47XtdcBQiktu5IsrPqOHe8w+sBmnLwwHt8wiUsWGTI=
-go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8=
-go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-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.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-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/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-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-20190108225652-1e06a53dbb7e/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
-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=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-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-20181221193216-37e7f081c4d4/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/sync v0.0.0-20210220032951-036812b2e83c/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210902050250-f475640dd07b/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-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
-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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
-golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
-golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
-golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
-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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-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/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
-lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw=
-modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/cc/v3 v3.35.19 h1:6ODWsJZWi6EwWeuC35hFBhin++9WWY3nThiS29Zl78U=
-modernc.org/cc/v3 v3.35.19/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
-modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
-modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
-modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
-modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
-modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
-modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
-modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
-modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
-modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
-modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
-modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
-modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
-modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
-modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
-modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
-modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
-modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
-modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
-modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
-modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
-modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
-modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
-modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
-modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
-modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
-modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
-modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
-modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
-modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
-modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
-modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
-modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
-modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
-modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
-modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
-modernc.org/ccgo/v3 v3.12.88/go.mod h1:0MFzUHIuSIthpVZyMWiFYMwjiFnhrN5MkvBrUwON+ZM=
-modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
-modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
-modernc.org/ccgo/v3 v3.12.95 h1:Ym2JG2G3P4IyZqjTTojHTl7qO0RysXeGSYPSoKPSBxc=
-modernc.org/ccgo/v3 v3.12.95/go.mod h1:ZcLyvtocXYi8uF+9Ebm3G8EF8HNY5hGomBqthDp4eC8=
-modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
-modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
-modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
-modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
-modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
-modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
-modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
-modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
-modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
-modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
-modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
-modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
-modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
-modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
-modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
-modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
-modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
-modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
-modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
-modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
-modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
-modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
-modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
-modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
-modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
-modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
-modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
-modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
-modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
-modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
-modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
-modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
-modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
-modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
-modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
-modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
-modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
-modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
-modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
-modernc.org/libc v1.11.90/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
-modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
-modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
-modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
-modernc.org/libc v1.11.104 h1:gxoa5b3HPo7OzD4tKZjgnwXk/w//u1oovvjSMP3Q96Q=
-modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
-modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
-modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
-modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
-modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
-modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
-modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.14.3 h1:psrTwgpEujgWEP3FNdsC9yNh5tSeA77U0GeWhHH4XmQ=
-modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
-modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
-modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
-modernc.org/tcl v1.9.2 h1:YA87dFLOsR2KqMka371a2Xgr+YsyUwo7OmHVSv/kztw=
-modernc.org/tcl v1.9.2/go.mod h1:aw7OnlIoiuJgu1gwbTZtrKnGpDqH9wyH++jZcxdqNsg=
-modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
-modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-modernc.org/z v1.2.20 h1:DyboxM1sJR2NB803j2StnbnL6jcQXz273OhHDGu8dGk=
-modernc.org/z v1.2.20/go.mod h1:zU9FiF4PbHdOTUxw+IF8j7ArBMRPsHgq10uVPt6xTzo=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/migrations/20241007210540_2_rating.go b/migrations/20241007210540_2_rating.go
new file mode 100644
index 0000000..06acb72
--- /dev/null
+++ b/migrations/20241007210540_2_rating.go
@@ -0,0 +1,33 @@
+package migrations
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/uptrace/bun"
+ "sh.org.ru/pkg/model"
+)
+
+func init() {
+ Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
+ fmt.Print(" [up migration] ")
+ if _, err := db.NewAddColumn().
+ Model((*model.Quote)(nil)).
+ ColumnExpr("rating INT NOT NULL DEFAULT '0'").
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ }, func(ctx context.Context, db *bun.DB) error {
+ fmt.Print(" [down migration] ")
+ if _, err := db.NewDropColumn().
+ Model((*model.Quote)(nil)).
+ Column("rating").
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 342012e..8e7402a 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -8,10 +8,12 @@ import (
)
type Config struct {
- Debug bool `yaml:"debug"`
- DB *db.Config `yaml:"db"`
- Listen string `yaml:"listen"`
- Admins map[string]string `yaml:"admins"`
+ Debug bool `yaml:"debug"`
+ DB *db.Config `yaml:"db"`
+ Listen string `yaml:"listen"`
+ Host string `yaml:"host"`
+ Admins map[string]string `yaml:"admins"`
+ Keypairs []string `yaml:"keys"`
}
func New(file string) (*Config, error) {
diff --git a/pkg/handler/add.go b/pkg/handler/add/add.go
index a6a5ced..dbcce47 100644
--- a/pkg/handler/add.go
+++ b/pkg/handler/add/add.go
@@ -1,4 +1,4 @@
-package handler
+package add
import (
"net/http"
@@ -40,7 +40,7 @@ func (h *Handler) AddQuotePost(c echo.Context) error {
Approved: false,
Archive: false,
}
- if _, err := h.DB.NewInsert().Model(q).Exec(c.Request().Context()); err != nil {
+ if _, err := h.db.NewInsert().Model(q).Exec(c.Request().Context()); err != nil {
return err
}
diff --git a/pkg/handler/add/handler.go b/pkg/handler/add/handler.go
new file mode 100644
index 0000000..8f744ab
--- /dev/null
+++ b/pkg/handler/add/handler.go
@@ -0,0 +1,21 @@
+package add
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/uptrace/bun"
+)
+
+type Handler struct {
+ db *bun.DB
+}
+
+// NewHandler returns new Handler.
+func NewHandler(db *bun.DB) *Handler {
+ return &Handler{db: db}
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.GET("", h.AddQuote)
+ g.POST("", h.AddQuotePost)
+ g.GET("/success", h.AddQuoteSuccess)
+}
diff --git a/pkg/handler/admin.go b/pkg/handler/admin/admin.go
index 75fb650..494da05 100644
--- a/pkg/handler/admin.go
+++ b/pkg/handler/admin/admin.go
@@ -1,16 +1,42 @@
-package handler
+package admin
import (
"net/http"
"github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+ "github.com/uptrace/bun"
+ "sh.org.ru/pkg/config"
"sh.org.ru/pkg/model"
"sh.org.ru/pkg/tpl"
)
+type Handler struct {
+ db *bun.DB
+ cfg *config.Config
+}
+
+// NewHandler returns new Handler.
+func NewHandler(db *bun.DB, cfg *config.Config) *Handler {
+ return &Handler{
+ db: db,
+ cfg: cfg,
+ }
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.Use(middleware.BasicAuth(func(u, p string, ctx echo.Context) (bool, error) {
+ return h.cfg.Admins[u] == p, nil
+ }))
+
+ g.GET("/", h.Admin)
+ g.POST("/action", h.AdminAction)
+ g.GET("/export", h.AdminExport)
+}
+
func (h *Handler) Admin(c echo.Context) error {
quotes := make([]model.Quote, 0, 20)
- count, err := h.DB.NewSelect().
+ count, err := h.db.NewSelect().
Model((*model.Quote)(nil)).
Order("id ASC").
Where("approved = ?", false).
@@ -30,7 +56,7 @@ func (h *Handler) AdminAction(c echo.Context) error {
switch form.Action {
case "approve":
- _, err := h.DB.NewUpdate().
+ _, err := h.db.NewUpdate().
Model(&model.Quote{
ID: int64(form.ID),
Approved: true,
@@ -42,7 +68,7 @@ func (h *Handler) AdminAction(c echo.Context) error {
return err
}
case "decline":
- _, err := h.DB.NewDelete().
+ _, err := h.db.NewDelete().
Model(&model.Quote{
ID: int64(form.ID),
}).
@@ -58,7 +84,7 @@ func (h *Handler) AdminAction(c echo.Context) error {
func (h *Handler) AdminExport(c echo.Context) error {
quotes := []model.Quote{}
- err := h.DB.NewSelect().
+ err := h.db.NewSelect().
Model((*model.Quote)(nil)).
Order("id ASC").
Scan(c.Request().Context(), &quotes)
diff --git a/pkg/handler/captcha/handler.go b/pkg/handler/captcha/handler.go
new file mode 100644
index 0000000..2e3b483
--- /dev/null
+++ b/pkg/handler/captcha/handler.go
@@ -0,0 +1,17 @@
+package captcha
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/ssoda/captcha"
+)
+
+type Handler struct{}
+
+// NewHandler returns new Handler.
+func NewHandler() *Handler {
+ return &Handler{}
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.GET("/*", echo.WrapHandler(captcha.Server(400, 65)))
+}
diff --git a/pkg/handler/feed/feed.go b/pkg/handler/feed/feed.go
new file mode 100644
index 0000000..05921e9
--- /dev/null
+++ b/pkg/handler/feed/feed.go
@@ -0,0 +1,73 @@
+package feed
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/feeds"
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+)
+
+func (h *Handler) Feed(c echo.Context) error {
+ feedType := c.Param("type")
+
+ quotes := make([]model.Quote, 0, 20)
+ err := h.db.NewSelect().
+ Model((*model.Quote)(nil)).
+ Order("id DESC").
+ Where("approved = ?", true).
+ Limit(20).
+ Scan(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ feed := &feeds.Feed{
+ Title: "sh.org.ru - Новый цитатник Рунета",
+ Link: &feeds.Link{Href: h.cfg.Host},
+ Description: "",
+ Author: &feeds.Author{Name: "NeonXP", Email: "i@neonxp.ru"},
+ Created: time.Now(),
+ }
+
+ for _, q := range quotes {
+ uid := fmt.Sprintf("%s/quote/%d", h.cfg.Host, q.ID)
+ feed.Items = append(feed.Items, &feeds.Item{
+ Id: uid,
+ Title: fmt.Sprintf("Цитата #%d", q.ID),
+ Link: &feeds.Link{Href: uid},
+ Created: q.CreatedAt,
+ Description: q.Text(),
+ })
+ }
+ switch feedType {
+ case "rss":
+ result, err := feed.ToRss()
+ if err != nil {
+ return err
+ }
+
+ c.Response().Header().Set("Content-Type", "application/rss+xml")
+ return c.String(http.StatusOK, result)
+ case "atom":
+ result, err := feed.ToAtom()
+ if err != nil {
+ return err
+ }
+
+ c.Response().Header().Set("Content-Type", "application/atom+xml")
+ return c.String(http.StatusOK, result)
+ case "json":
+ result, err := feed.ToJSON()
+ if err != nil {
+ return err
+ }
+
+ c.Response().Header().Set("Content-Type", "application/json")
+ return c.String(http.StatusOK, result)
+ default:
+ return echo.ErrNotFound
+ }
+}
diff --git a/pkg/handler/feed/handler.go b/pkg/handler/feed/handler.go
new file mode 100644
index 0000000..868e477
--- /dev/null
+++ b/pkg/handler/feed/handler.go
@@ -0,0 +1,21 @@
+package feed
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/uptrace/bun"
+ "sh.org.ru/pkg/config"
+)
+
+type Handler struct {
+ db *bun.DB
+ cfg *config.Config
+}
+
+// NewHandler returns new Handler.
+func NewHandler(db *bun.DB, cfg *config.Config) *Handler {
+ return &Handler{db: db, cfg: cfg}
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.GET("/:type", h.Feed)
+}
diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go
index 5ba3966..15eb42d 100644
--- a/pkg/handler/handler.go
+++ b/pkg/handler/handler.go
@@ -1,7 +1,15 @@
package handler
-import "github.com/uptrace/bun"
+import "github.com/labstack/echo/v4"
-type Handler struct {
- DB *bun.DB
+type Handler interface {
+ Register(g *echo.Group)
+}
+
+type Router map[string]Handler
+
+func (r Router) Register(e *echo.Echo) {
+ for groupName, handlers := range r {
+ handlers.Register(e.Group(groupName))
+ }
}
diff --git a/pkg/handler/quote/handler.go b/pkg/handler/quote/handler.go
new file mode 100644
index 0000000..04807c0
--- /dev/null
+++ b/pkg/handler/quote/handler.go
@@ -0,0 +1,26 @@
+package quote
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/uptrace/bun"
+)
+
+type Handler struct {
+ db *bun.DB
+}
+
+// NewHandler returns new Handler.
+func NewHandler(db *bun.DB) *Handler {
+ return &Handler{db: db}
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.GET("", h.Index)
+ g.GET("quote/:id", h.Quote)
+ g.GET("random", h.Random)
+ g.GET("top", h.Top)
+}
+
+type Pagination struct {
+ Page int `query:"page" default:"0"`
+}
diff --git a/pkg/handler/index.go b/pkg/handler/quote/index.go
index 611a544..9dd21f8 100644
--- a/pkg/handler/index.go
+++ b/pkg/handler/quote/index.go
@@ -1,4 +1,4 @@
-package handler
+package quote
import (
"github.com/labstack/echo/v4"
@@ -13,7 +13,7 @@ func (h *Handler) Index(c echo.Context) error {
}
quotes := make([]model.Quote, 0, 20)
- count, err := h.DB.NewSelect().
+ count, err := h.db.NewSelect().
Model((*model.Quote)(nil)).
Order("id DESC").
Where("approved = ?", true).
@@ -24,9 +24,5 @@ func (h *Handler) Index(c echo.Context) error {
return err
}
- return tpl.Index(quotes, p.Page, count).Render(c.Request().Context(), c.Response())
-}
-
-type Pagination struct {
- Page int `query:"page" default:"0"`
+ return tpl.List(quotes, p.Page, count).Render(c.Request().Context(), c.Response())
}
diff --git a/pkg/handler/quote.go b/pkg/handler/quote/quote.go
index 2a5f7e6..b25eb90 100644
--- a/pkg/handler/quote.go
+++ b/pkg/handler/quote/quote.go
@@ -1,4 +1,4 @@
-package handler
+package quote
import (
"strconv"
@@ -16,7 +16,7 @@ func (h *Handler) Quote(c echo.Context) error {
}
quote := new(model.Quote)
- err = h.DB.NewSelect().
+ err = h.db.NewSelect().
Model(quote).
Where("id = ?", id).Scan(c.Request().Context(), quote)
if err != nil {
diff --git a/pkg/handler/quote/random.go b/pkg/handler/quote/random.go
new file mode 100644
index 0000000..1e04ff0
--- /dev/null
+++ b/pkg/handler/quote/random.go
@@ -0,0 +1,18 @@
+package quote
+
+import (
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Random(c echo.Context) error {
+ quotes := make([]model.Quote, 0, 20)
+ err := h.db.NewRaw(`select q.* from quotes q where q.approved = true order by random() limit 20`).
+ Scan(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ return tpl.Random(quotes).Render(c.Request().Context(), c.Response())
+}
diff --git a/pkg/handler/quote/top.go b/pkg/handler/quote/top.go
new file mode 100644
index 0000000..c2803ea
--- /dev/null
+++ b/pkg/handler/quote/top.go
@@ -0,0 +1,28 @@
+package quote
+
+import (
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Top(c echo.Context) error {
+ p := &Pagination{}
+ if err := c.Bind(p); err != nil {
+ return err
+ }
+
+ quotes := make([]model.Quote, 0, 20)
+ count, err := h.db.NewSelect().
+ Model((*model.Quote)(nil)).
+ Order("rating DESC").
+ Where("approved = ?", true).
+ Limit(20).
+ Offset(p.Page*20).
+ ScanAndCount(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ return tpl.List(quotes, p.Page, count).Render(c.Request().Context(), c.Response())
+}
diff --git a/pkg/handler/random.go b/pkg/handler/random.go
deleted file mode 100644
index 29c5f6f..0000000
--- a/pkg/handler/random.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package handler
-
-import (
- "github.com/a-h/templ"
- "github.com/labstack/echo/v4"
- "sh.org.ru/pkg/model"
- "sh.org.ru/pkg/tpl"
-)
-
-func (h *Handler) Random(c echo.Context) error {
- quotes := make([]model.Quote, 0, 20)
- err := h.DB.NewRaw(`select q.* from quotes q where q.approved = true order by random() limit 20`).
- Scan(c.Request().Context(), &quotes)
- if err != nil {
- return err
- }
-
- comp := tpl.Random(quotes)
-
- if c.Request().Header.Get("Hx-Request") == "true" {
- return comp.Render(c.Request().Context(), c.Response())
- }
-
- ctx := templ.WithChildren(c.Request().Context(), comp)
-
- return tpl.Layout(tpl.HeaderParams{
- Title: "Цитатник Рунета",
- Description: "Новый цитатник Рунета",
- URL: "https://sh.org.ru/",
- }).Render(ctx, c.Response())
-}
diff --git a/pkg/handler/rate/handler.go b/pkg/handler/rate/handler.go
new file mode 100644
index 0000000..1a2ecf3
--- /dev/null
+++ b/pkg/handler/rate/handler.go
@@ -0,0 +1,19 @@
+package rate
+
+import (
+ "github.com/labstack/echo/v4"
+ "github.com/uptrace/bun"
+)
+
+type Handler struct {
+ db *bun.DB
+}
+
+// NewHandler returns new Handler.
+func NewHandler(db *bun.DB) *Handler {
+ return &Handler{db: db}
+}
+
+func (h *Handler) Register(g *echo.Group) {
+ g.POST("/:id", h.Rate)
+}
diff --git a/pkg/handler/rate/rate.go b/pkg/handler/rate/rate.go
new file mode 100644
index 0000000..df6713f
--- /dev/null
+++ b/pkg/handler/rate/rate.go
@@ -0,0 +1,69 @@
+package rate
+
+import (
+ "strconv"
+
+ "github.com/labstack/echo-contrib/session"
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Rate(c echo.Context) error {
+ voted, err := session.Get("votes", c)
+ if err != nil {
+ return err
+ }
+ sid := c.Param("id")
+ id, err := strconv.Atoi(sid)
+ if err != nil {
+ return err
+ }
+ f := new(rateForm)
+ if err := c.Bind(f); err != nil {
+ return err
+ }
+
+ quote := new(model.Quote)
+
+ if _, ok := voted.Values[id]; !ok {
+ voted.Values[id] = true
+ if err := voted.Save(c.Request(), c.Response()); err != nil {
+ return err
+ }
+ set := ""
+ switch f.Vote {
+ case "up":
+ set = "rating = rating + 1"
+ case "down":
+ set = "rating = rating - 1"
+ }
+ _, err = h.db.NewUpdate().
+ Model(quote).
+ Where("id = ?", id).
+ Set(set).
+ Returning("*").
+ Exec(c.Request().Context(), quote)
+ } else {
+ err = h.db.NewSelect().
+ Model(quote).
+ Where("id = ?", id).
+ Scan(c.Request().Context(), quote)
+ }
+ if err != nil {
+ return err
+ }
+
+ return tpl.Rate(quote, 0).Render(c.Request().Context(), c.Response())
+}
+
+type rateForm struct {
+ Vote string `form:"vote"`
+}
+
+type voteType string
+
+const (
+ voteUp voteType = "up"
+ voteDown voteType = "down"
+)
diff --git a/pkg/middleware/context.go b/pkg/middleware/context.go
new file mode 100644
index 0000000..f9c4425
--- /dev/null
+++ b/pkg/middleware/context.go
@@ -0,0 +1,21 @@
+package middleware
+
+import (
+ "context"
+
+ "github.com/labstack/echo/v4"
+)
+
+type ContextKey string
+
+func Context(key ContextKey, value any) echo.MiddlewareFunc {
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ ctx := context.WithValue(c.Request().Context(), key, value)
+ r := c.Request().WithContext(ctx)
+ c.SetRequest(r)
+
+ return next(c)
+ }
+ }
+}
diff --git a/pkg/middleware/session/store.go b/pkg/middleware/session/store.go
new file mode 100644
index 0000000..04071c9
--- /dev/null
+++ b/pkg/middleware/session/store.go
@@ -0,0 +1,230 @@
+package session
+
+import (
+ "context"
+ "encoding/base32"
+ "log/slog"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gorilla/securecookie"
+ "github.com/gorilla/sessions"
+ "github.com/uptrace/bun"
+)
+
+const sessionIDLen = 32
+const defaultTableName = "sessions"
+const defaultMaxAge = 60 * 60 * 24 * 30 // 30 days
+const defaultPath = "/"
+
+// Options for bunstore
+type Options struct {
+ TableName string
+ SkipCreateTable bool
+}
+
+// Store represent a bunstore
+type Store struct {
+ db *bun.DB
+ opts Options
+ Codecs []securecookie.Codec
+ SessionOpts *sessions.Options
+}
+
+type bunSession struct {
+ bun.BaseModel `bun:"table:sessions,alias:s"`
+
+ ID string `bun:",pk,unique"`
+ Data string
+ CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
+ UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
+ ExpiresAt time.Time
+}
+
+type KeyPairs []string
+
+func (k KeyPairs) ToKeys() [][]byte {
+ b := make([][]byte, 0, len(k))
+ for _, kk := range k {
+ b = append(b, []byte(kk))
+ }
+ return b
+}
+
+// New creates a new bunstore session
+func New(db *bun.DB, keyPairs KeyPairs) (*Store, error) {
+ return NewOptions(db, Options{}, keyPairs)
+}
+
+// NewOptions creates a new bunstore session with options
+func NewOptions(db *bun.DB, opts Options, keyPairs KeyPairs) (*Store, error) {
+ st := &Store{
+ db: db,
+ opts: opts,
+ Codecs: securecookie.CodecsFromPairs(keyPairs.ToKeys()...),
+ SessionOpts: &sessions.Options{
+ Path: defaultPath,
+ MaxAge: defaultMaxAge,
+ },
+ }
+ if st.opts.TableName == "" {
+ st.opts.TableName = defaultTableName
+ }
+
+ if !st.opts.SkipCreateTable {
+ model := &bunSession{}
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+ if _, err := db.NewCreateTable().IfNotExists().Model(model).Exec(ctx); err != nil {
+ return nil, err
+ }
+ if _, err := db.NewCreateIndex().Model(model).Column("expires_at").Exec(ctx); err != nil {
+ return nil, err
+ }
+ }
+
+ return st, nil
+}
+
+// Get returns a session for the given name after adding it to the registry.
+func (st *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
+ return sessions.GetRegistry(r).Get(st, name)
+}
+
+// New creates a session with name without adding it to the registry.
+func (st *Store) New(r *http.Request, name string) (*sessions.Session, error) {
+ session := sessions.NewSession(st, name)
+ opts := *st.SessionOpts
+ session.Options = &opts
+ session.IsNew = true
+
+ st.MaxAge(st.SessionOpts.MaxAge)
+
+ // try fetch from db if there is a cookie
+ s := st.getSessionFromCookie(r, session.Name())
+ if s != nil {
+ if err := securecookie.DecodeMulti(session.Name(), s.Data, &session.Values, st.Codecs...); err != nil {
+ return session, nil
+ }
+ session.ID = s.ID
+ session.IsNew = false
+ }
+
+ return session, nil
+}
+
+// Save session and set cookie header
+func (st *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
+ s := st.getSessionFromCookie(r, session.Name())
+
+ // delete if max age is < 0
+ if session.Options.MaxAge < 0 {
+ if s != nil {
+ if _, err := st.db.NewDelete().Model(&bunSession{ID: session.ID}).Exec(r.Context()); err != nil {
+ return err
+ }
+ }
+ http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
+ return nil
+ }
+
+ data, err := securecookie.EncodeMulti(session.Name(), session.Values, st.Codecs...)
+ if err != nil {
+ return err
+ }
+ now := time.Now()
+ expire := now.Add(time.Second * time.Duration(session.Options.MaxAge))
+
+ if s == nil {
+ // generate random session ID key suitable for storage in the db
+ session.ID = strings.TrimRight(
+ base32.StdEncoding.EncodeToString(
+ securecookie.GenerateRandomKey(sessionIDLen)), "=")
+ s = &bunSession{
+ ID: session.ID,
+ Data: data,
+ ExpiresAt: expire,
+ }
+ if _, err := st.db.NewInsert().Model(s).Exec(r.Context()); err != nil {
+ return err
+ }
+ } else {
+ s.Data = data
+ s.ExpiresAt = expire
+ if _, err := st.db.NewUpdate().Model(s).WherePK("id").Column("data", "expires_at").Exec(r.Context()); err != nil {
+ return err
+ }
+ }
+
+ // set session id cookie
+ id, err := securecookie.EncodeMulti(session.Name(), s.ID, st.Codecs...)
+ if err != nil {
+ return err
+ }
+ http.SetCookie(w, sessions.NewCookie(session.Name(), id, session.Options))
+
+ return nil
+}
+
+// getSessionFromCookie looks for an existing bunSession from a session ID stored inside a cookie
+func (st *Store) getSessionFromCookie(r *http.Request, name string) *bunSession {
+ if cookie, err := r.Cookie(name); err == nil {
+ sessionID := ""
+ if err := securecookie.DecodeMulti(name, cookie.Value, &sessionID, st.Codecs...); err != nil {
+ return nil
+ }
+ s := &bunSession{}
+ err := st.db.NewSelect().Model(s).Where("id = ? AND expires_at > ?", sessionID, time.Now()).Scan(r.Context())
+ if err != nil {
+ return nil
+ }
+ return s
+ }
+ return nil
+}
+
+// MaxAge sets the maximum age for the store and the underlying cookie
+// implementation. Individual sessions can be deleted by setting
+// Options.MaxAge = -1 for that session.
+func (st *Store) MaxAge(age int) {
+ st.SessionOpts.MaxAge = age
+ for _, codec := range st.Codecs {
+ if sc, ok := codec.(*securecookie.SecureCookie); ok {
+ sc.MaxAge(age)
+ }
+ }
+}
+
+// MaxLength restricts the maximum length of new sessions to l.
+// If l is 0 there is no limit to the size of a session, use with caution.
+// The default is 4096 (default for securecookie)
+func (st *Store) MaxLength(l int) {
+ for _, c := range st.Codecs {
+ if codec, ok := c.(*securecookie.SecureCookie); ok {
+ codec.MaxLength(l)
+ }
+ }
+}
+
+// Cleanup deletes expired sessions
+func (st *Store) Cleanup() {
+ _, err := st.db.NewDelete().Model(&bunSession{}).Where("expires_at <= ?", time.Now()).Exec(context.Background())
+ if err != nil {
+ slog.Default().With("error", err).Error("cleanup")
+ }
+}
+
+// PeriodicCleanup runs Cleanup every interval. Close quit channel to stop.
+func (st *Store) PeriodicCleanup(interval time.Duration, quit <-chan struct{}) {
+ t := time.NewTicker(interval)
+ defer t.Stop()
+ for {
+ select {
+ case <-t.C:
+ st.Cleanup()
+ case <-quit:
+ return
+ }
+ }
+}
diff --git a/pkg/model/quote.go b/pkg/model/quote.go
index 0ad89cf..1e397b8 100644
--- a/pkg/model/quote.go
+++ b/pkg/model/quote.go
@@ -14,6 +14,7 @@ type Quote struct {
Quote string `bun:",notnull"`
Approved bool
Archive bool
+ Rating int
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}
diff --git a/pkg/tpl/add.templ b/pkg/tpl/add.templ
index 14b57eb..82d76e5 100644
--- a/pkg/tpl/add.templ
+++ b/pkg/tpl/add.templ
@@ -2,7 +2,10 @@ package tpl
import "fmt"
+var captchaHandler = templ.NewOnceHandle()
+
templ AddQuotePage(form *AddQuoteForm, err string) {
+ {{ captchaURL := fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID) }}
@Layout(HeaderParams{}) {
<h2>Добавление цитаты</h2>
if err != "" {
@@ -14,10 +17,27 @@ templ AddQuotePage(form *AddQuoteForm, err string) {
<form method="post">
<textarea rows="5" name="quote" placeholder="Текст цитаты">{ form.Quote }</textarea>
<input type="hidden" name="captcha_id" value={ form.CaptchaID }/>
- <img class="captcha" src={ fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID) }/>
- <input type="text" name="captcha_value" placeholder="Код с картинки"/>
+ <label for="captcha_value">
+ <img class="captcha" id="captcha" src={ captchaURL }/>
+ <a
+ role="button"
+ data-url={ captchaURL }
+ onclick="reloadCaptcha(this)"
+ >
+ <i class="fa fa-refresh"></i>&nbsp;Обновить капчу
+ </a>
+ </label>
+ <input type="text" name="captcha_value" id="captcha_value" placeholder="Код с картинки"/>
<input type="submit" value="Отправить на модерацию"/>
</form>
+ @captchaHandler.Once() {
+ <script type="text/javascript">
+ function reloadCaptcha(event) {
+ const url = event.getAttribute('data-url');
+ document.getElementById('captcha').setAttribute('src', url+'?reload='+Math.random());
+ }
+ </script>
+ }
}
}
diff --git a/pkg/tpl/add_templ.go b/pkg/tpl/add_templ.go
index d47582b..b0731ac 100644
--- a/pkg/tpl/add_templ.go
+++ b/pkg/tpl/add_templ.go
@@ -10,6 +10,8 @@ import templruntime "github.com/a-h/templ/runtime"
import "fmt"
+var captchaHandler = templ.NewOnceHandle()
+
func AddQuotePage(form *AddQuoteForm, err string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -31,6 +33,7 @@ func AddQuotePage(form *AddQuoteForm, err string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
+ captchaURL := fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -55,7 +58,7 @@ func AddQuotePage(form *AddQuoteForm, err string) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 11, Col: 9}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 14, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -73,7 +76,7 @@ func AddQuotePage(form *AddQuoteForm, err string) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(form.Quote)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 15, Col: 85}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 18, Col: 85}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -86,26 +89,61 @@ func AddQuotePage(form *AddQuoteForm, err string) templ.Component {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(form.CaptchaID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 16, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 19, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <img class=\"captcha\" src=\"")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"captcha_value\"><img class=\"captcha\" id=\"captcha\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
- templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID))
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(captchaURL)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 17, Col: 85}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 21, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <input type=\"text\" name=\"captcha_value\" placeholder=\"Код с картинки\"> <input type=\"submit\" value=\"Отправить на модерацию\"></form>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <a role=\"button\" data-url=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(captchaURL)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 24, Col: 26}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" onclick=\"reloadCaptcha(this)\"><i class=\"fa fa-refresh\"></i>&nbsp;Обновить капчу</a></label> <input type=\"text\" name=\"captcha_value\" id=\"captcha_value\" placeholder=\"Код с картинки\"> <input type=\"submit\" value=\"Отправить на модерацию\"></form>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var8 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script type=\"text/javascript\">\n\t\t\t\tfunction reloadCaptcha(event) {\n\t\t\t\t\tconst url = event.getAttribute('data-url');\n\t\t\t\t\tdocument.getElementById('captcha').setAttribute('src', url+'?reload='+Math.random());\n\t\t\t\t}\n\t\t\t</script>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = captchaHandler.Once().Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/tpl/layout.templ b/pkg/tpl/layout.templ
index c777457..d96aa50 100644
--- a/pkg/tpl/layout.templ
+++ b/pkg/tpl/layout.templ
@@ -10,9 +10,13 @@ templ Layout(params HeaderParams) {
<link rel="stylesheet" href="/css/pico.css"/>
<link rel="stylesheet" href="/css/style.css"/>
<link rel="stylesheet" href="/css/fork-awesome.min.css"/>
+ <link rel="alternate" type="application/rss+xml" title="RSS feed" href="/feed/rss">
+ <link rel="alternate" type="application/atom+xml" title="ATOM feed" href="/feed/atom">
+ <link rel="alternate" type="application/json" title="json feed" href="/feed/json">
<meta property="og:title" content={ params.Title }/>
<meta property="og:url" content={ params.URL }/>
<meta property="og:description" content={ params.Description }/>
+ <meta name="yandex-verification" content="ee0e23da00ce9fe4" />
<title>ШОргРу</title>
</head>
<body>
@@ -21,9 +25,11 @@ templ Layout(params HeaderParams) {
<ul>
<li><a href="/"><strong>ШОргРу</strong></a></li>
</ul>
- <ul>
+ <ul hx-boost="true" hx-indicator=".loader">
+ <span aria-busy="true" class="loader htmx-indicator">Загрузка...</span>
<li><a href="/">Главная</a></li>
<li><a href="/random">Случайные</a></li>
+ <li><a href="/top">Топ</a></li>
<li><a href="/add">Добавить цитату</a></li>
</ul>
</nav>
diff --git a/pkg/tpl/layout_templ.go b/pkg/tpl/layout_templ.go
index a86bb30..10b0bfc 100644
--- a/pkg/tpl/layout_templ.go
+++ b/pkg/tpl/layout_templ.go
@@ -29,14 +29,14 @@ func Layout(params HeaderParams) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"/css/pico.css\"><link rel=\"stylesheet\" href=\"/css/style.css\"><link rel=\"stylesheet\" href=\"/css/fork-awesome.min.css\"><meta property=\"og:title\" content=\"")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"/css/pico.css\"><link rel=\"stylesheet\" href=\"/css/style.css\"><link rel=\"stylesheet\" href=\"/css/fork-awesome.min.css\"><link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS feed\" href=\"/feed/rss\"><link rel=\"alternate\" type=\"application/atom+xml\" title=\"ATOM feed\" href=\"/feed/atom\"><link rel=\"alternate\" type=\"application/json\" title=\"json feed\" href=\"/feed/json\"><meta property=\"og:title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(params.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 13, Col: 51}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 16, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -49,7 +49,7 @@ func Layout(params HeaderParams) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(params.URL)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 14, Col: 47}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 17, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -62,13 +62,13 @@ func Layout(params HeaderParams) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(params.Description)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 15, Col: 63}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 18, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><title>ШОргРу</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\"><strong>ШОргРу</strong></a></li></ul><ul><li><a href=\"/\">Главная</a></li><li><a href=\"/random\">Случайные</a></li><li><a href=\"/add\">Добавить цитату</a></li></ul></nav>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"yandex-verification\" content=\"ee0e23da00ce9fe4\"><title>ШОргРу</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\"><strong>ШОргРу</strong></a></li></ul><ul hx-boost=\"true\" hx-indicator=\".loader\"><span aria-busy=\"true\" class=\"loader htmx-indicator\">Загрузка...</span><li><a href=\"/\">Главная</a></li><li><a href=\"/random\">Случайные</a></li><li><a href=\"/top\">Топ</a></li><li><a href=\"/add\">Добавить цитату</a></li></ul></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/tpl/index.templ b/pkg/tpl/list.templ
index 3655531..014a2db 100644
--- a/pkg/tpl/index.templ
+++ b/pkg/tpl/list.templ
@@ -2,21 +2,25 @@ package tpl
import (
"fmt"
+ "sh.org.ru/pkg/config"
"sh.org.ru/pkg/model"
"strconv"
+ "sh.org.ru/pkg/middleware"
)
-templ Index(quotes []model.Quote, page, count int) {
+templ List(quotes []model.Quote, page, count int) {
+ {{ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host }}
@Layout(HeaderParams{
Title: "Цитатник Рунета",
Description: "Новый цитатник Рунета",
- URL: "https://sh.org.ru/",
+ URL: host,
}) {
for _, q := range quotes {
@Quote(&q)
}
+ <span aria-busy="true" class="loader htmx-indicator">Загрузка...</span>
<nav>
- <ul hx-boost="true">
+ <ul hx-boost="true" hx-indicator=".loader">
if page > 0 {
<li><a href={ templ.URL(fmt.Sprintf("/?page=%d", page-1)) }>&larr;</a></li>
}
diff --git a/pkg/tpl/index_templ.go b/pkg/tpl/list_templ.go
index eabd30d..2ed9497 100644
--- a/pkg/tpl/index_templ.go
+++ b/pkg/tpl/list_templ.go
@@ -10,11 +10,13 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
+ "sh.org.ru/pkg/config"
+ "sh.org.ru/pkg/middleware"
"sh.org.ru/pkg/model"
"strconv"
)
-func Index(quotes []model.Quote, page, count int) templ.Component {
+func List(quotes []model.Quote, page, count int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -35,6 +37,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
+ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -53,7 +56,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
return templ_7745c5c3_Err
}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <nav><ul hx-boost=\"true\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <span aria-busy=\"true\" class=\"loader htmx-indicator\">Загрузка...</span><nav><ul hx-boost=\"true\" hx-indicator=\".loader\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -86,7 +89,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(p)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 27, Col: 14}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/list.templ`, Line: 31, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -113,7 +116,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(p)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 29, Col: 64}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/list.templ`, Line: 33, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -147,7 +150,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(count))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 38, Col: 34}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/list.templ`, Line: 42, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@@ -162,7 +165,7 @@ func Index(quotes []model.Quote, page, count int) templ.Component {
templ_7745c5c3_Err = Layout(HeaderParams{
Title: "Цитатник Рунета",
Description: "Новый цитатник Рунета",
- URL: "https://sh.org.ru/",
+ URL: host,
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
diff --git a/pkg/tpl/quote.templ b/pkg/tpl/quote.templ
index 34429d1..7b38faa 100644
--- a/pkg/tpl/quote.templ
+++ b/pkg/tpl/quote.templ
@@ -2,11 +2,14 @@ package tpl
import (
"fmt"
+ "sh.org.ru/pkg/config"
+ "sh.org.ru/pkg/middleware"
"sh.org.ru/pkg/model"
"strconv"
)
templ Quote(quote *model.Quote) {
+ {{ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host }}
<article>
<header>
<a href={ templ.URL(fmt.Sprintf("/quote/%d", quote.ID)) }>#{ strconv.Itoa(int(quote.ID)) }</a>
@@ -14,24 +17,24 @@ templ Quote(quote *model.Quote) {
</header>
@templ.Raw(quote.Text())
<footer>
- <span>
- <a target="_blank" href={ templ.URL(fmt.Sprintf("https://t.me/share/url?url=https://sh.org.ru/quote/%d", quote.ID)) }><i class="fa fa-telegram" aria-hidden="true"></i></a>&nbsp;&middot;&nbsp
- <a target="_blank" href={ templ.URL(fmt.Sprintf("https://vk.com/share.php?url=https://sh.org.ru/quote/%d", quote.ID)) }><i class="fa fa-vk" aria-hidden="true"></i></a>&nbsp;&middot;&nbsp
- <a target="_blank" href={ templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=https://sh.org.ru/quote/%d", quote.ID)) }><i class="fa fa-odnoklassniki-square" aria-hidden="true"></i></a>
- </span>
+ @Rate(quote, 0)
<span>
if quote.Archive {
<abbr title="Цитата из старого цитатника">Архив</abbr>
}
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://t.me/share/url?url=%s/quote/%d", host, quote.ID)) }><i class="fa fa-telegram" aria-hidden="true"></i></a>&nbsp;&middot;&nbsp
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://vk.com/share.php?url=%s/quote/%d", host, quote.ID)) }><i class="fa fa-vk" aria-hidden="true"></i></a>&nbsp;&middot;&nbsp
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=%s/quote/%d", host, quote.ID)) }><i class="fa fa-odnoklassniki-square" aria-hidden="true"></i></a>
</span>
</footer>
</article>
}
templ QuotePage(quote *model.Quote) {
+ {{ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host }}
@Layout(HeaderParams{
- Title: "Цитата #" + strconv.Itoa(int(quote.ID)),
- URL: fmt.Sprintf("https://sh.org.ru/quote/%d", quote.ID),
+ Title: "Цитата #" + strconv.Itoa(int(quote.ID)),
+ URL: fmt.Sprintf("%s/quote/%d", host, quote.ID),
Description: templ.EscapeString(quote.Quote),
}) {
@Quote(quote)
diff --git a/pkg/tpl/quote_templ.go b/pkg/tpl/quote_templ.go
index fa139cd..cc799cc 100644
--- a/pkg/tpl/quote_templ.go
+++ b/pkg/tpl/quote_templ.go
@@ -10,6 +10,8 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
+ "sh.org.ru/pkg/config"
+ "sh.org.ru/pkg/middleware"
"sh.org.ru/pkg/model"
"strconv"
)
@@ -35,6 +37,7 @@ func Quote(quote *model.Quote) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
+ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -51,7 +54,7 @@ func Quote(quote *model.Quote) templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(quote.ID)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote.templ`, Line: 12, Col: 91}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote.templ`, Line: 15, Col: 91}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -64,7 +67,7 @@ func Quote(quote *model.Quote) templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(quote.CreatedAt.Format("02.01.06"))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote.templ`, Line: 13, Col: 92}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote.templ`, Line: 16, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -78,11 +81,29 @@ func Quote(quote *model.Quote) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer><span><a target=\"_blank\" href=\"")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("https://t.me/share/url?url=https://sh.org.ru/quote/%d", quote.ID))
+ templ_7745c5c3_Err = Rate(quote, 0).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if quote.Archive {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<abbr title=\"Цитата из старого цитатника\">Архив</abbr> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a target=\"_blank\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("https://t.me/share/url?url=%s/quote/%d", host, quote.ID))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -91,7 +112,7 @@ func Quote(quote *model.Quote) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(fmt.Sprintf("https://vk.com/share.php?url=https://sh.org.ru/quote/%d", quote.ID))
+ var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(fmt.Sprintf("https://vk.com/share.php?url=%s/quote/%d", host, quote.ID))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -100,22 +121,12 @@ func Quote(quote *model.Quote) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=https://sh.org.ru/quote/%d", quote.ID))
+ var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=%s/quote/%d", host, quote.ID))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><i class=\"fa fa-odnoklassniki-square\" aria-hidden=\"true\"></i></a></span> <span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if quote.Archive {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<abbr title=\"Цитата из старого цитатника\">Архив</abbr>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></footer></article>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><i class=\"fa fa-odnoklassniki-square\" aria-hidden=\"true\"></i></a></span></footer></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -144,6 +155,7 @@ func QuotePage(quote *model.Quote) templ.Component {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
+ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host
templ_7745c5c3_Var9 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -164,7 +176,7 @@ func QuotePage(quote *model.Quote) templ.Component {
})
templ_7745c5c3_Err = Layout(HeaderParams{
Title: "Цитата #" + strconv.Itoa(int(quote.ID)),
- URL: fmt.Sprintf("https://sh.org.ru/quote/%d", quote.ID),
+ URL: fmt.Sprintf("%s/quote/%d", host, quote.ID),
Description: templ.EscapeString(quote.Quote),
}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
diff --git a/pkg/tpl/random.templ b/pkg/tpl/random.templ
index 39ce3ff..2d25a03 100644
--- a/pkg/tpl/random.templ
+++ b/pkg/tpl/random.templ
@@ -1,12 +1,33 @@
package tpl
import (
+ "sh.org.ru/pkg/config"
+ "sh.org.ru/pkg/middleware"
"sh.org.ru/pkg/model"
)
templ Random(quotes []model.Quote) {
- for _, q := range quotes {
- @Quote(&q)
+ {{ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host }}
+ @Layout(HeaderParams{
+ Title: "Цитатник Рунета",
+ Description: "Новый цитатник Рунета",
+ URL: host,
+ }) {
+ <div id="random">
+ for _, q := range quotes {
+ @Quote(&q)
+ }
+ <a
+ role="button"
+ hx-get="/random"
+ hx-swap="outerHTML"
+ hx-select="#random"
+ hx-target="#random"
+ hx-indicator="#loader"
+ >
+ Загрузить ещё...
+ </a>
+ <span aria-busy="true" id="loader" class="htmx-indicator">Загрузка...</span>
+ </div>
}
- <a role="button" hx-get="/random" hx-swap="outerHTML">Загрузить ещё...</a>
}
diff --git a/pkg/tpl/random_templ.go b/pkg/tpl/random_templ.go
index 79f5685..803bf22 100644
--- a/pkg/tpl/random_templ.go
+++ b/pkg/tpl/random_templ.go
@@ -9,6 +9,8 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
+ "sh.org.ru/pkg/config"
+ "sh.org.ru/pkg/middleware"
"sh.org.ru/pkg/model"
)
@@ -33,13 +35,40 @@ func Random(quotes []model.Quote) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- for _, q := range quotes {
- templ_7745c5c3_Err = Quote(&q).Render(ctx, templ_7745c5c3_Buffer)
+ host := ctx.Value(middleware.ContextKey("config")).(*config.Config).Host
+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"random\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a role=\"button\" hx-get=\"/random\" hx-swap=\"outerHTML\">Загрузить ещё...</a>")
+ for _, q := range quotes {
+ templ_7745c5c3_Err = Quote(&q).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a role=\"button\" hx-get=\"/random\" hx-swap=\"outerHTML\" hx-select=\"#random\" hx-target=\"#random\" hx-indicator=\"#loader\">Загрузить ещё...</a> <span aria-busy=\"true\" id=\"loader\" class=\"htmx-indicator\">Загрузка...</span></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{
+ Title: "Цитатник Рунета",
+ Description: "Новый цитатник Рунета",
+ URL: host,
+ }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/tpl/rate.templ b/pkg/tpl/rate.templ
new file mode 100644
index 0000000..bd63df8
--- /dev/null
+++ b/pkg/tpl/rate.templ
@@ -0,0 +1,31 @@
+package tpl
+
+import (
+ "fmt"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+templ Rate(quote *model.Quote, act int) {
+ <nav class="rate">
+ <a
+ hx-post={ fmt.Sprintf("/rate/%d", quote.ID) }
+ hx-target="closest .rate"
+ hx-vals='{"vote": "up"}'
+ href="#"
+ >
+ <i class="fa fa-plus"></i>
+ </a>
+ &nbsp;
+ { strconv.Itoa(quote.Rating) }
+ &nbsp;
+ <a
+ hx-post={ fmt.Sprintf("/rate/%d", quote.ID) }
+ hx-target="closest .rate"
+ hx-vals='{"vote": "down"}'
+ href="#"
+ >
+ <i class="fa fa-minus"></i>
+ </a>
+ </nav>
+}
diff --git a/pkg/tpl/rate_templ.go b/pkg/tpl/rate_templ.go
new file mode 100644
index 0000000..6c27477
--- /dev/null
+++ b/pkg/tpl/rate_templ.go
@@ -0,0 +1,85 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "fmt"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+func Rate(quote *model.Quote, act int) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"rate\"><a hx-post=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/rate/%d", quote.ID))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/rate.templ`, Line: 12, Col: 46}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"closest .rate\" hx-vals=\"{&#34;vote&#34;: &#34;up&#34;}\" href=\"#\"><i class=\"fa fa-plus\"></i></a> &nbsp; ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(quote.Rating))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/rate.templ`, Line: 20, Col: 30}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" &nbsp; <a hx-post=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/rate/%d", quote.ID))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/rate.templ`, Line: 23, Col: 46}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"closest .rate\" hx-vals=\"{&#34;vote&#34;: &#34;down&#34;}\" href=\"#\"><i class=\"fa fa-minus\"></i></a></nav>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/static/css/style.css b/static/css/style.css
index d1b4d40..db74cac 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -12,3 +12,14 @@ article footer {
flex-direction: row;
justify-content: space-between;
}
+
+.htmx-indicator{
+ opacity:0;
+ transition: opacity 500ms ease-in;
+}
+.htmx-request .htmx-indicator{
+ opacity:1;
+}
+.htmx-request.htmx-indicator{
+ opacity:1;
+} \ No newline at end of file