aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile31
-rw-r--r--app/cmd/migrate.go99
-rw-r--r--app/cmd/root.go1
-rw-r--r--app/cmd/serve.go90
-rw-r--r--app/cmd/user.go17
-rwxr-xr-xbuild-docker.sh8
-rw-r--r--controllers/post.go42
-rw-r--r--controllers/requests.go (renamed from routes/requests.go)18
-rw-r--r--controllers/routes.go19
-rw-r--r--controllers/topic.go103
-rw-r--r--controllers/user.go59
-rw-r--r--go.mod42
-rw-r--r--go.sum152
-rw-r--r--middleware/session/store.go30
-rw-r--r--middleware/user.go38
-rw-r--r--migrations/1_schema.down.sql2
-rw-r--r--migrations/1_schema.up.sql27
-rw-r--r--migrations/fs.go6
-rw-r--r--models/errors.go4
-rw-r--r--models/node.go59
-rw-r--r--models/user.go15
-rw-r--r--repository/node.go58
-rw-r--r--repository/post.go70
-rw-r--r--repository/topic.go101
-rw-r--r--repository/user.go98
-rw-r--r--routes/node.go129
-rw-r--r--routes/routes.go17
-rw-r--r--routes/user.go86
-rw-r--r--utils/date.go7
-rw-r--r--utils/middleware.go8
-rw-r--r--views/error.templ2
-rw-r--r--views/error_templ.go2
-rw-r--r--views/layouts.templ27
-rw-r--r--views/layouts_templ.go62
-rw-r--r--views/login.templ2
-rw-r--r--views/login_templ.go2
-rw-r--r--views/new.templ59
-rw-r--r--views/new_templ.go132
-rw-r--r--views/nodes.templ99
-rw-r--r--views/nodes_templ.go377
-rw-r--r--views/posts.templ72
-rw-r--r--views/posts_templ.go238
-rw-r--r--views/register.templ2
-rw-r--r--views/register_templ.go2
-rw-r--r--views/topics.templ45
-rw-r--r--views/topics_templ.go144
46 files changed, 1298 insertions, 1405 deletions
diff --git a/Dockerfile b/Dockerfile
index 5b46639..b3f1639 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,18 +1,39 @@
# syntax=docker/dockerfile:1
-FROM golang:1.22.5-alpine3.20 AS builder
+FROM --platform=$BUILDPLATFORM golang:1.22.5-alpine3.20 AS builder
+
+ARG TARGETARCH
+
+ARG TARGETOS
+
+ENV CGO_ENABLED 0
+
+ENV GOOS linux
+
+RUN apk update --no-cache && apk add --no-cache tzdata ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
+
RUN go mod download
COPY . .
-RUN CGO_ENABLED=0 GOOS=linux go build -o /app/gorum
+RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-s -w" -o /app/gorum
+
+FROM --platform=$BUILDPLATFORM scratch
+
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+
+COPY --from=builder /usr/share/zoneinfo/Europe/Moscow /usr/share/zoneinfo/Europe/Moscow
+
+ENV TZ Europe/Moscow
+
+WORKDIR /app
-FROM alpine:3.20
+COPY --from=builder /app/gorum /app/gorum
-COPY --from=builder /app/gorum .
+EXPOSE 8000
-ENTRYPOINT ["/gorum"] \ No newline at end of file
+ENTRYPOINT ["/app/gorum"] \ No newline at end of file
diff --git a/app/cmd/migrate.go b/app/cmd/migrate.go
deleted file mode 100644
index 95a1dc3..0000000
--- a/app/cmd/migrate.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package cmd
-
-import (
- "database/sql"
- "fmt"
-
- "github.com/golang-migrate/migrate/v4"
- "github.com/golang-migrate/migrate/v4/database/sqlite"
- "github.com/golang-migrate/migrate/v4/source/iofs"
- "github.com/spf13/cobra"
- "gitrepo.ru/neonxp/gorum/migrations"
-)
-
-var migrateCmd = &cobra.Command{
- Use: "migrate",
- Short: "Migrate db",
- Long: `Up and down migrations`,
-}
-
-func init() {
- migrateCmd.AddCommand(
- &cobra.Command{
- Use: "up",
- Short: "Migrate up",
- Long: `Up migrations`,
- RunE: up,
- },
- &cobra.Command{
- Use: "down",
- Short: "Migrate down",
- Long: `Down migrations`,
- RunE: down,
- },
- &cobra.Command{
- Use: "drop",
- Short: "Drop db",
- Long: `Deletes everything in the database`,
- RunE: drop,
- },
- )
-}
-
-func up(_ *cobra.Command, _ []string) error {
- m, err := initMigrate()
- if err != nil {
- return fmt.Errorf("open migration failed: %w", err)
- }
- defer m.Close()
- if err := m.Up(); err != nil && err != migrate.ErrNoChange {
- return fmt.Errorf("do migration failed: %w", err)
- }
-
- return nil
-}
-
-func down(_ *cobra.Command, _ []string) error {
- m, err := initMigrate()
- if err != nil {
- return fmt.Errorf("open migration failed: %w", err)
- }
- defer m.Close()
- if err := m.Down(); err != nil && err != migrate.ErrNoChange {
- return fmt.Errorf("do migration failed: %w", err)
- }
-
- return nil
-}
-
-func drop(_ *cobra.Command, _ []string) error {
- m, err := initMigrate()
- if err != nil {
- return fmt.Errorf("open migration failed: %w", err)
- }
- defer m.Close()
- if err := m.Drop(); err != nil && err != migrate.ErrNoChange {
- return fmt.Errorf("do migration failed: %w", err)
- }
-
- return nil
-}
-
-func initMigrate() (*migrate.Migrate, error) {
- db, err := sql.Open("sqlite3", dbFile)
- if err != nil {
- return nil, fmt.Errorf("open db failed: %w", err)
- }
- defer db.Close()
-
- driver, err := sqlite.WithInstance(db, &sqlite.Config{})
- if err != nil {
- return nil, fmt.Errorf("failed create migration driver: %w", err)
- }
- sourceDriver, err := iofs.New(migrations.FS, ".")
- if err != nil {
- return nil, fmt.Errorf("failed open migrations: %w", err)
- }
-
- return migrate.NewWithInstance("fs", sourceDriver, "sqlite3", driver)
-}
diff --git a/app/cmd/root.go b/app/cmd/root.go
index 5803166..7d1c731 100644
--- a/app/cmd/root.go
+++ b/app/cmd/root.go
@@ -25,7 +25,6 @@ func init() {
viper.BindPFlag("db", serverCmd.Flags().Lookup("db"))
rootCmd.AddCommand(serverCmd)
- rootCmd.AddCommand(migrateCmd)
rootCmd.AddCommand(userCmd)
}
diff --git a/app/cmd/serve.go b/app/cmd/serve.go
index e2d2843..578de1e 100644
--- a/app/cmd/serve.go
+++ b/app/cmd/serve.go
@@ -7,21 +7,19 @@ import (
"net"
"net/http"
- "github.com/labstack/echo-contrib/session"
- "github.com/labstack/echo/v4"
- echomiddleware "github.com/labstack/echo/v4/middleware"
_ "github.com/mattn/go-sqlite3"
- "github.com/michaeljs1990/sqlitestore"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitrepo.ru/neonxp/gorum/contextlib"
- "gitrepo.ru/neonxp/gorum/db"
- "gitrepo.ru/neonxp/gorum/middleware"
+ "gitrepo.ru/neonxp/gorum/controllers"
+ appmiddleware "gitrepo.ru/neonxp/gorum/middleware"
"gitrepo.ru/neonxp/gorum/repository"
- "gitrepo.ru/neonxp/gorum/routes"
- "gitrepo.ru/neonxp/gorum/utils"
"gitrepo.ru/neonxp/gorum/views"
"gitrepo.ru/neonxp/gorum/views/assets"
+ "go.etcd.io/bbolt"
+ "go.neonxp.ru/mux"
+ "go.neonxp.ru/mux/middleware"
+ "go.neonxp.ru/mux/middleware/session"
)
var (
@@ -57,7 +55,7 @@ func serve(ctx context.Context) error {
)
ctx = context.WithValue(ctx, contextlib.ThemeKey, theme)
- orm, err := db.GetDB(dbFile)
+ orm, err := bbolt.Open(dbFile, 0600, nil)
if err != nil {
return err
}
@@ -65,59 +63,51 @@ func serve(ctx context.Context) error {
defer orm.Close()
userRepo := repository.NewUser(orm)
- nodeRepo := repository.NewNode(orm)
+ nodeRepo := repository.NewPost(orm)
+ topicRepo := repository.NewTopic(orm)
- r := routes.NewRouter(userRepo, nodeRepo)
-
- e := echo.New()
-
- e.HideBanner = true
-
- e.HTTPErrorHandler = func(err error, c echo.Context) {
- _ = utils.Render(c, views.ErrorPage(err))
+ if err := topicRepo.Init(); err != nil {
+ return err
}
-
- sessionStore, err := sqlitestore.NewSqliteStoreFromConnection(orm.DB, "sessions", "", 0, []byte(sessionSecret))
- if err != nil {
- return fmt.Errorf("failed init session store: %w", err)
+ if err := userRepo.Init(); err != nil {
+ return err
}
- e.Use(
- echomiddleware.Recover(),
- echomiddleware.Gzip(),
- // echomiddleware.CSRFWithConfig(echomiddleware.CSRFConfig{
- // Skipper: echomiddleware.DefaultSkipper,
- // TokenLength: 32,
- // TokenLookup: "form:" + echo.HeaderXCSRFToken,
- // ContextKey: "csrf",
- // CookieName: "_csrf",
- // CookieMaxAge: 86400,
- // }),
- session.Middleware(sessionStore),
- middleware.UserMiddleware(),
- )
+ r := controllers.NewRouter(userRepo, nodeRepo, topicRepo)
- e.GET("/register", r.Register)
- e.POST("/register", r.Register)
- e.GET("/login", r.Login)
- e.POST("/login", r.Login)
- e.POST("/logout", r.Logout)
+ e := http.NewServeMux()
- e.GET("/", r.Node)
- e.GET("/p/:id", r.Node)
- e.GET("/p/:id/new", r.NewPost)
- e.POST("/p/:id/new", r.NewPost)
- e.GET("/t/:id", r.Node)
- e.GET("/t/:id/new", r.NewTopic)
- e.POST("/t/:id/new", r.NewTopic)
+ mux.DefaultErrorHandler = func(err error) mux.Renderer {
+ return views.ErrorPage(err)
+ }
- e.StaticFS("/assets", assets.FS)
+ e.HandleFunc("GET /register", r.Register)
+ e.HandleFunc("POST /register", r.Register)
+ e.HandleFunc("GET /login", r.Login)
+ e.HandleFunc("POST /login", r.Login)
+ e.HandleFunc("POST /logout", r.Logout)
+
+ e.HandleFunc("GET /{$}", r.Index)
+ e.HandleFunc("GET /t/{id}", r.Topic)
+ e.HandleFunc("GET /t/new", r.NewTopic)
+ e.HandleFunc("POST /t/new", r.NewTopic)
+ e.HandleFunc("POST /p/new", r.NewPost)
+
+ e.Handle("/assets/", http.StripPrefix("/assets", http.FileServerFS(assets.FS)))
+
+ mh := mux.Use(e,
+ middleware.Logger(slog.Default()),
+ middleware.Recover(slog.Default()),
+ middleware.RequestID,
+ appmiddleware.UserMiddleware(),
+ session.Middleware(session.DefaultConfig, session.NewBoltStore(orm, []byte("sessions"))),
+ )
slog.InfoContext(ctx, "started gorum", slog.String("bind", listen))
server := http.Server{
Addr: listen,
- Handler: e,
+ Handler: mh,
ErrorLog: slog.NewLogLogger(slog.Default().Handler(), slog.LevelError),
ConnContext: func(cctx context.Context, c net.Conn) context.Context {
return ctx
diff --git a/app/cmd/user.go b/app/cmd/user.go
index 673c7b8..1f88c6f 100644
--- a/app/cmd/user.go
+++ b/app/cmd/user.go
@@ -6,9 +6,9 @@ import (
"os"
"github.com/spf13/cobra"
- "gitrepo.ru/neonxp/gorum/db"
"gitrepo.ru/neonxp/gorum/models"
"gitrepo.ru/neonxp/gorum/repository"
+ "go.etcd.io/bbolt"
)
var userCmd = &cobra.Command{
@@ -21,7 +21,7 @@ var createUserCmd = &cobra.Command{
Args: cobra.ExactArgs(3),
ArgAliases: []string{"username", "email", "role"},
RunE: func(cmd *cobra.Command, args []string) error {
- orm, err := db.GetDB(dbFile)
+ orm, err := bbolt.Open(dbFile, 0600, nil)
if err != nil {
return fmt.Errorf("failed init db: %w", err)
}
@@ -40,12 +40,11 @@ var createUserCmd = &cobra.Command{
password, _ := reader.ReadString('\n')
ur := repository.NewUser(orm)
- id, err := ur.Create(cmd.Context(), email, password, username, iRole)
- if err != nil {
+ if err := ur.Create(email, password, username, iRole); err != nil {
return fmt.Errorf("failed create user: %w", err)
}
- fmt.Printf("Created user %s (id=%d, email=%s, role_id=%d)\n", username, id, email, iRole)
+ fmt.Printf("Created user %s (email=%s, role_id=%d)\n", username, email, iRole)
return nil
},
@@ -54,20 +53,20 @@ var createUserCmd = &cobra.Command{
var listUserCmd = &cobra.Command{
Use: "list",
RunE: func(cmd *cobra.Command, args []string) error {
- orm, err := db.GetDB(dbFile)
+ orm, err := bbolt.Open(dbFile, 0600, nil)
if err != nil {
return fmt.Errorf("failed init db: %w", err)
}
ur := repository.NewUser(orm)
- users, err := ur.List(cmd.Context())
+ users, err := ur.List()
if err != nil {
return err
}
- fmt.Printf("ID\tUsername\tEmail\tRole\n")
+ fmt.Printf("Username\tEmail\tRole\n")
for _, u := range users {
- fmt.Printf("%d\t%s\t%s\t%d\n", u.ID, u.Username, u.Email, u.Role)
+ fmt.Printf("%s\t%s\t%d\n", u.Username, u.Email, u.Role)
}
return nil
diff --git a/build-docker.sh b/build-docker.sh
new file mode 100755
index 0000000..dfcc96d
--- /dev/null
+++ b/build-docker.sh
@@ -0,0 +1,8 @@
+#! /bin/sh
+buildah manifest rm gorum-manifest || true
+buildah manifest create gorum-manifest
+for PLATFORM in linux/amd64 linux/arm64/v8 # linux/386 linux/arm/v5 linux/arm/v6 linux/arm/v7 linux/arm/v8 linux/s390x linux/ppc64le
+do
+ buildah bud --manifest gorum-manifest --platform ${PLATFORM}
+done
+buildah manifest push --all --format v2s2 gorum-manifest docker://gitrepo.ru/neonxp/gorum:latest \ No newline at end of file
diff --git a/controllers/post.go b/controllers/post.go
new file mode 100644
index 0000000..aee7c97
--- /dev/null
+++ b/controllers/post.go
@@ -0,0 +1,42 @@
+package controllers
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+
+ "gitrepo.ru/neonxp/gorum/contextlib"
+ "gitrepo.ru/neonxp/gorum/views"
+ "go.neonxp.ru/mux"
+)
+
+func (rt *Router) NewPost(w http.ResponseWriter, r *http.Request) {
+ req := new(postRequest)
+ if err := mux.Bind(r, req); err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ user := contextlib.GetUser(r.Context())
+ if user == nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(errors.New("403")))
+ return
+ }
+
+ topic, err := rt.topicRepo.Get(uint64(req.Parent))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+
+ if r.Method == http.MethodPost {
+ postID, err := rt.postRepo.CreatePost(req.Text, user.Email, uint64(req.Parent))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ mux.Redirect(w, 302, fmt.Sprintf("/t/%d#post%d", req.Parent, postID))
+ return
+ }
+
+ mux.Render(w, r, views.NewPost(topic))
+}
diff --git a/routes/requests.go b/controllers/requests.go
index a0bc13f..487b7cc 100644
--- a/routes/requests.go
+++ b/controllers/requests.go
@@ -1,11 +1,9 @@
-package routes
-
-import "gitrepo.ru/neonxp/gorum/models"
+package controllers
type loginRequest struct {
Email string `form:"email"`
Password string `form:"password"`
- Remember string `form:"remember"`
+ Remember bool `form:"remember"`
}
type registerRequest struct {
@@ -15,7 +13,13 @@ type registerRequest struct {
Password2 string `form:"password2"`
}
-type nodeRequest struct {
- Type models.NodeType `form:"type"`
- Text string `form:"text"`
+type topicRequest struct {
+ Parent int `form:"parent"`
+ Topic string `form:"topic"`
+ Text string `form:"text"`
+}
+
+type postRequest struct {
+ Parent int `form:"parent"`
+ Text string `form:"text"`
}
diff --git a/controllers/routes.go b/controllers/routes.go
new file mode 100644
index 0000000..6e2ab9b
--- /dev/null
+++ b/controllers/routes.go
@@ -0,0 +1,19 @@
+package controllers
+
+import (
+ "gitrepo.ru/neonxp/gorum/repository"
+)
+
+type Router struct {
+ userRepo *repository.User
+ postRepo *repository.Post
+ topicRepo *repository.Topic
+}
+
+func NewRouter(userRepo *repository.User, nodeRepo *repository.Post, topicRepo *repository.Topic) *Router {
+ return &Router{
+ userRepo: userRepo,
+ postRepo: nodeRepo,
+ topicRepo: topicRepo,
+ }
+}
diff --git a/controllers/topic.go b/controllers/topic.go
new file mode 100644
index 0000000..50f65e7
--- /dev/null
+++ b/controllers/topic.go
@@ -0,0 +1,103 @@
+package controllers
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "gitrepo.ru/neonxp/gorum/contextlib"
+ "gitrepo.ru/neonxp/gorum/models"
+ "gitrepo.ru/neonxp/gorum/views"
+ "go.neonxp.ru/mux"
+)
+
+func (rt *Router) Index(w http.ResponseWriter, r *http.Request) {
+ topics, err := rt.topicRepo.List(0)
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+
+ mux.Render(w, r, views.Topics(topics))
+}
+
+func (rt *Router) Topic(w http.ResponseWriter, r *http.Request) {
+ sParentID := r.PathValue("id")
+ parentID, err := strconv.Atoi(sParentID)
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ topic, err := rt.topicRepo.Get(uint64(parentID))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+
+ topics, err := rt.topicRepo.List(uint64(parentID))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ posts, err := rt.postRepo.List(uint64(parentID))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ emailToUser := map[string]*models.User{}
+ for _, p := range posts {
+ u, ok := emailToUser[p.AuthorID]
+ if !ok {
+ u, err = rt.userRepo.Get(p.AuthorID)
+ if err != nil {
+ u = &models.User{Username: "user-not-found"}
+ }
+ }
+ emailToUser[p.AuthorID] = u
+ p.Author = u
+ }
+
+ mux.Render(w, r, views.Posts(topic, topics, posts))
+}
+
+func (rt *Router) NewTopic(w http.ResponseWriter, r *http.Request) {
+ req := new(topicRequest)
+ if err := mux.Bind(r, req); err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ user := contextlib.GetUser(r.Context())
+ if user == nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(errors.New("403")))
+ return
+ }
+
+ if r.Method == http.MethodPost {
+ topic, err := rt.topicRepo.Create(req.Topic, req.Text, user.Email, uint64(req.Parent))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ mux.Redirect(w, 302, fmt.Sprintf("/t/%d", topic.ID))
+ return
+ }
+
+ topic := &models.Topic{
+ ID: 0,
+ Topic: "Корень",
+ }
+
+ sTopicID := r.PathValue("topic")
+ if sTopicID != "" {
+ if topicID, err := strconv.Atoi(sTopicID); err == nil {
+ topic, err = rt.topicRepo.Get(uint64(topicID))
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ }
+ }
+
+ mux.Render(w, r, views.NewTopic(topic))
+}
diff --git a/controllers/user.go b/controllers/user.go
new file mode 100644
index 0000000..a605c25
--- /dev/null
+++ b/controllers/user.go
@@ -0,0 +1,59 @@
+package controllers
+
+import (
+ "net/http"
+
+ "gitrepo.ru/neonxp/gorum/models"
+ "gitrepo.ru/neonxp/gorum/views"
+ "go.neonxp.ru/mux"
+ "go.neonxp.ru/mux/middleware/session"
+)
+
+func (rt *Router) Login(w http.ResponseWriter, r *http.Request) {
+ req := new(loginRequest)
+
+ if r.Method == http.MethodPost {
+ if err := mux.Bind(r, req); err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ u, err := rt.userRepo.Login(req.Email, req.Password)
+ if err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ sess := session.FromRequest(r)
+
+ (*sess)["user"] = u
+
+ mux.Redirect(w, 302, "/")
+ return
+ }
+
+ mux.Render(w, r, views.Login(req.Email))
+}
+
+func (rt *Router) Logout(w http.ResponseWriter, r *http.Request) {
+ session.Clear(w, r)
+
+ mux.Redirect(w, 302, "/")
+}
+
+func (rt *Router) Register(w http.ResponseWriter, r *http.Request) {
+ req := new(registerRequest)
+
+ if err := mux.Bind(r, req); err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ if r.Method == http.MethodPost {
+ if err := rt.userRepo.Create(req.Email, req.Password, req.Username, models.RoleUser); err != nil {
+ mux.Render(w, r, mux.DefaultErrorHandler(err))
+ return
+ }
+ mux.Redirect(w, 302, "/login")
+ return
+ }
+
+ mux.Render(w, r, views.Register(req.Username, req.Email))
+}
diff --git a/go.mod b/go.mod
index 62a24ec..1012083 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,8 @@ go 1.22.5
require (
github.com/a-h/templ v0.2.747
+ github.com/go-session/echo-session v3.0.0+incompatible
+ github.com/go-session/session v2.4.0+incompatible
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024
github.com/gorilla/sessions v1.3.0
github.com/labstack/echo-contrib v0.17.1
@@ -11,63 +13,63 @@ require (
)
require (
+ github.com/bytedance/gopkg v0.0.0-20221122125632-68358b8ecec6 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
- github.com/google/uuid v1.4.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
- github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
- github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/labstack/echo v3.3.10+incompatible // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
- github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+ github.com/satori/go.uuid v1.2.0 // indirect
+ github.com/smartystreets/goconvey v1.8.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
+ github.com/tidwall/btree v1.4.2 // indirect
+ github.com/tidwall/buntdb v1.3.1 // indirect
+ github.com/tidwall/gjson v1.14.3 // indirect
+ github.com/tidwall/grect v0.1.4 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/tidwall/rtred v0.1.2 // indirect
+ github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
- go.uber.org/atomic v1.11.0 // indirect
+ go.etcd.io/bbolt v1.3.10 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
- golang.org/x/mod v0.17.0 // indirect
- golang.org/x/tools v0.13.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
- lukechampine.com/uint128 v1.2.0 // indirect
- modernc.org/cc/v3 v3.36.3 // indirect
- modernc.org/ccgo/v3 v3.16.9 // indirect
- modernc.org/libc v1.17.1 // indirect
- modernc.org/mathutil v1.5.0 // indirect
- modernc.org/memory v1.2.1 // indirect
- modernc.org/opt v0.1.3 // indirect
- modernc.org/sqlite v1.18.1 // indirect
- modernc.org/strutil v1.1.3 // indirect
- modernc.org/token v1.0.0 // indirect
)
require (
+ github.com/dimuska139/go-email-normalizer/v2 v2.0.1
+ github.com/go-session/session/v3 v3.2.1
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
- github.com/golang-migrate/migrate/v4 v4.17.1
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.22
- github.com/michaeljs1990/sqlitestore v0.0.0-20210507162135-8585425bc864
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/uptrace/bun v1.2.1
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
+ github.com/wagslane/go-password-validator v0.3.0
+ go.neonxp.ru/objectid v0.0.2
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.21.0 // indirect
diff --git a/go.sum b/go.sum
index 3273127..ff68c79 100644
--- a/go.sum
+++ b/go.sum
@@ -1,75 +1,79 @@
github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
+github.com/bytedance/gopkg v0.0.0-20221122125632-68358b8ecec6 h1:FCLDGi1EmB7JzjVVYNZiqc/zAJj2BQ5M0lfkVOxbfs8=
+github.com/bytedance/gopkg v0.0.0-20221122125632-68358b8ecec6/go.mod h1:5FoAH5xUHHCMDvQPy1rnj8moqLkLHFaDVBjHhcFwEi0=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-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/dimuska139/go-email-normalizer/v2 v2.0.1 h1:axEFQ9XgXQjZKUwMT52WhJEbCuZXcTAPBqbgW+EPQJw=
+github.com/dimuska139/go-email-normalizer/v2 v2.0.1/go.mod h1:2Gil1j/rfUKJ5BHc/uxxyRiuk3YTg6/C3D7dz7jVQfw=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-session/echo-session v3.0.0+incompatible h1:yJejVUrmpOF+sZSRfhO2fC/5TNCa3BkgQIVhMykwY44=
+github.com/go-session/echo-session v3.0.0+incompatible/go.mod h1:bUiKI2tqw16QA15uld4e6WV8BnPYROuzqm3+fECvrII=
+github.com/go-session/session v2.4.0+incompatible h1:lSgyN6s0GJO1P5/EIad5N2jwI1dszDT8aES24KHl2vM=
+github.com/go-session/session v2.4.0+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
+github.com/go-session/session/v3 v3.2.1 h1:APQf5JFW84+bhbqRjEZO8J+IppSgT1jMQTFI/XVyIFY=
+github.com/go-session/session/v3 v3.2.1/go.mod h1:RftEBbyuzqkNCAxIrCLJe+rfBqB/4G11qxq9KYKrx4M=
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-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
-github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024 h1:saBP362Qm7zDdDXqv61kI4rzhmLFq3Z1gx34xpl6cWE=
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
-github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
-github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
+github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
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/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
-github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
-github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
+github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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.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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/michaeljs1990/sqlitestore v0.0.0-20210507162135-8585425bc864 h1:NkqeBeGMAmwEr0CibX80gHlrX7hSQSmdKpTaPex5n9c=
-github.com/michaeljs1990/sqlitestore v0.0.0-20210507162135-8585425bc864/go.mod h1:N6aiMetO+sSN0h4VC8RjkwiljKaZmgPsWzZG+mk6oec=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
@@ -77,8 +81,6 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -86,6 +88,12 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
+github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
+github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
+github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@@ -102,6 +110,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -109,6 +120,27 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
+github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
+github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
+github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
+github.com/tidwall/buntdb v1.3.1 h1:HKoDF01/aBhl9RjYtbaLnvX9/OuenwvQiC3OP1CcL4o=
+github.com/tidwall/buntdb v1.3.1/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
+github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
+github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
+github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
+github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
+github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
+github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
+github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
+github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
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.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
@@ -123,54 +155,30 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU
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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
-go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
+github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
+go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
+go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
+go.neonxp.ru/objectid v0.0.2 h1:Z/G6zvBxmUq0NTq681oGH8pTbBWwi6VA22YOYludIPs=
+go.neonxp.ru/objectid v0.0.2/go.mod h1:s0dRi//oe1liiKcor1KmWx09WzkD6Wtww8ZaIv+VLBs=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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=
@@ -179,39 +187,3 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
-lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
-lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
-lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
-modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
-modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg=
-modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
-modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM=
-modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
-modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
-modernc.org/ccorpus v1.11.6/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.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
-modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI=
-modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
-modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
-modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
-modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
-modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs=
-modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
-modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
-modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8=
-modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
-modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
-modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
-modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
-modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao=
-modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
-modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
-modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
-modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
diff --git a/middleware/session/store.go b/middleware/session/store.go
deleted file mode 100644
index f22c64d..0000000
--- a/middleware/session/store.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package session
-
-import (
- "net/http"
-
- "github.com/gorilla/sessions"
- "github.com/uptrace/bun"
-)
-
-type SessionStore struct {
- orm *bun.DB
-}
-
-// Get should return a cached session.
-func (s *SessionStore) Get(r *http.Request, name string) (*sessions.Session, error) {
- panic("not implemented") // TODO: Implement
-}
-
-// New should create and return a new session.
-//
-// Note that New should never return a nil session, even in the case of
-// an error if using the Registry infrastructure to cache the session.
-func (s *SessionStore) New(r *http.Request, name string) (*sessions.Session, error) {
- panic("not implemented") // TODO: Implement
-}
-
-// Save should persist session to the underlying store implementation.
-func (s *SessionStore) Save(r *http.Request, w http.ResponseWriter, ss *sessions.Session) error {
- panic("not implemented") // TODO: Implement
-}
diff --git a/middleware/user.go b/middleware/user.go
index eaca6e9..f4eb12a 100644
--- a/middleware/user.go
+++ b/middleware/user.go
@@ -2,33 +2,25 @@ package middleware
import (
"context"
+ "net/http"
- "github.com/labstack/echo-contrib/session"
- "github.com/labstack/echo/v4"
"gitrepo.ru/neonxp/gorum/contextlib"
- "gitrepo.ru/neonxp/gorum/models"
+ "go.neonxp.ru/mux"
+ "go.neonxp.ru/mux/middleware/session"
)
-func UserMiddleware() echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c echo.Context) error {
- sess, err := session.Get("session", c)
- if err != nil {
- return err
+func UserMiddleware() mux.Middleware {
+ return func(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ session := session.FromRequest(r)
+ user := (*session)["user"]
+ if user == nil {
+ h.ServeHTTP(w, r)
+ return
}
- u, okSess := sess.Values["user"]
- if !okSess {
- return next(c)
- }
- user, okUser := u.(models.User)
- if !okUser {
- return next(c)
- }
- ctx := context.WithValue(c.Request().Context(), contextlib.UserKey, user)
- req := c.Request().WithContext(ctx)
- c.SetRequest(req)
-
- return next(c)
- }
+ h.ServeHTTP(w, r.WithContext(
+ context.WithValue(r.Context(), contextlib.UserKey, user),
+ ))
+ })
}
}
diff --git a/migrations/1_schema.down.sql b/migrations/1_schema.down.sql
deleted file mode 100644
index c0a3f9d..0000000
--- a/migrations/1_schema.down.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-DROP TABLE "nodes";
--- DROP TABLE "users";
diff --git a/migrations/1_schema.up.sql b/migrations/1_schema.up.sql
deleted file mode 100644
index 2f8bc04..0000000
--- a/migrations/1_schema.up.sql
+++ /dev/null
@@ -1,27 +0,0 @@
-CREATE TABLE IF NOT EXISTS "users" (
- "id" INTEGER,
- "email" TEXT NOT NULL,
- "password" TEXT NOT NULL,
- "username" TEXT NOT NULL,
- "photo" TEXT,
- "role" INTEGER DEFAULT(0),
- PRIMARY KEY ("id" AUTOINCREMENT),
- UNIQUE ("email" COLLATE NOCASE),
- UNIQUE ("username" COLLATE NOCASE)
-);
-
-CREATE TABLE IF NOT EXISTS "nodes" (
- "id" INTEGER,
- "type" INTEGER,
- "text" TEXT,
- "author_id" INTEGER NOT NULL,
- "parent_id" INTEGER,
- "created_at" INTEGER NOT NULL,
- "updated_at" INTEGER NOT NULL,
- "deleted_at" INTEGER,
- "permission" INTEGER,
- PRIMARY KEY ("id" AUTOINCREMENT),
- FOREIGN KEY ("author_id") REFERENCES "users" ("id"),
- FOREIGN KEY ("parent_id") REFERENCES "posts" ("id")
-);
-
diff --git a/migrations/fs.go b/migrations/fs.go
deleted file mode 100644
index 91cca1c..0000000
--- a/migrations/fs.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package migrations
-
-import "embed"
-
-//go:embed *.sql
-var FS embed.FS
diff --git a/models/errors.go b/models/errors.go
index 660fe85..9b514e9 100644
--- a/models/errors.go
+++ b/models/errors.go
@@ -6,4 +6,8 @@ var (
ErrInvalidUserOrPassword = errors.New("invalid user or password")
ErrUserAlreadyExists = errors.New("user already exists")
ErrInvalidPassword = errors.New("invalid password")
+ ErrTopicNotFound = errors.New("topic not found")
+ ErrPostNotFound = errors.New("post not found")
+ ErrUserNotFound = errors.New("user not found")
+ ErrDBNotInitialized = errors.New("db is not initialized")
)
diff --git a/models/node.go b/models/node.go
index 98a8d4e..7d3f719 100644
--- a/models/node.go
+++ b/models/node.go
@@ -1,49 +1,36 @@
package models
import (
- "context"
"time"
-
- "github.com/uptrace/bun"
)
-type Node struct {
- bun.BaseModel `bun:"table:nodes,alias:n"`
-
- ID int `bun:"id,pk,autoincrement"`
- Type NodeType
- Text string
- AuthorID int
- Author *User `bun:"rel:belongs-to,join:author_id=id"`
- ParentID int
- Parent *Node `bun:"rel:belongs-to,join:parent_id=id"`
- Permission int
- CreatedAt int64 `bun:",nullzero,notnull,default:current_timestamp"`
- UpdatedAt int64 `bun:",nullzero,notnull,default:current_timestamp"`
- DeletedAt int64
- Children []*Node `bun:"rel:has-many,join:id=parent_id"`
+type Topic struct {
+ ID uint64 `json:"id"`
+ Topic string `json:"topic"`
+ Text string `json:"text"`
+ AuthorID string `json:"author_id"`
+ Author *User `json:"-"`
+ ParentID uint64 `json:"parent_id"`
+ Parent *Topic `json:"-"`
+ Permission int `json:"permission"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt *time.Time `json:"deleted_at"`
}
-var _ bun.BeforeAppendModelHook = (*Node)(nil)
-
-func (m *Node) BeforeAppendModel(ctx context.Context, query bun.Query) error {
- switch query.(type) {
- case *bun.InsertQuery:
- m.CreatedAt = time.Now().Unix()
- m.UpdatedAt = time.Now().Unix()
- case *bun.UpdateQuery:
- m.UpdatedAt = time.Now().Unix()
- }
- return nil
+type Post struct {
+ ID uint64 `json:"id"`
+ Text string `json:"text"`
+ AuthorID string `json:"author_id"`
+ Author *User `json:"-"`
+ ParentID uint64 `json:"parent_id"`
+ Parent *Post `json:"-"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt *time.Time `json:"deleted_at"`
+ Children []*Post `json:"-"`
}
-type NodeType int
-
-const (
- TopicType NodeType = iota
- PostType
-)
-
type Permission int
const (
diff --git a/models/user.go b/models/user.go
index 73358fe..e8ae3cc 100644
--- a/models/user.go
+++ b/models/user.go
@@ -2,8 +2,6 @@ package models
import (
"encoding/gob"
-
- "github.com/uptrace/bun"
)
func init() {
@@ -11,14 +9,11 @@ func init() {
}
type User struct {
- bun.BaseModel `bun:"table:users,alias:u"`
-
- ID int `bun:"id,pk,autoincrement"`
- Email string
- Password string
- Username string
- Photo *string
- Role UserRole
+ Email string `json:"email,omitempty"`
+ Password string `json:"password,omitempty"`
+ Username string `json:"username,omitempty"`
+ Photo *string `json:"photo,omitempty"`
+ Role UserRole `json:"role,omitempty"`
}
type UserRole int
diff --git a/repository/node.go b/repository/node.go
deleted file mode 100644
index 27282af..0000000
--- a/repository/node.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package repository
-
-import (
- "context"
-
- "github.com/uptrace/bun"
- "gitrepo.ru/neonxp/gorum/models"
-)
-
-type Node struct {
- db *bun.DB
-}
-
-func NewNode(db *bun.DB) *Node {
- return &Node{
- db: db,
- }
-}
-
-func (t *Node) Create(
- ctx context.Context,
- ntype models.NodeType,
- text string,
- authorID int,
- parentID int,
-) (int, error) {
- post := &models.Node{
- Type: ntype,
- Text: text,
- AuthorID: authorID,
- ParentID: parentID,
- }
- _, err := t.db.NewInsert().Model(post).Returning("id").Exec(ctx)
-
- return post.ID, err
-}
-
-func (t *Node) Get(ctx context.Context, topicID int) (*models.Node, error) {
- node := new(models.Node)
-
- return node, t.db.NewSelect().
- Model(node).
- Where(`n.id = ?`, topicID).
- Relation("Author").
- Relation("Parent").
- Scan(ctx)
-}
-
-func (t *Node) List(ctx context.Context, topicID int) ([]*models.Node, error) {
- posts := make([]*models.Node, 0)
-
- return posts, t.db.NewSelect().
- Model(&posts).
- Where(`parent_id = ?`, topicID).
- Relation("Author").
- Relation("Children").
- Scan(ctx)
-}
diff --git a/repository/post.go b/repository/post.go
new file mode 100644
index 0000000..1b0fef6
--- /dev/null
+++ b/repository/post.go
@@ -0,0 +1,70 @@
+package repository
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "time"
+
+ "gitrepo.ru/neonxp/gorum/models"
+ "go.etcd.io/bbolt"
+)
+
+type Post struct {
+ db *bbolt.DB
+}
+
+func NewPost(db *bbolt.DB) *Post {
+ return &Post{
+ db: db,
+ }
+}
+
+func (t *Post) CreatePost(
+ text string,
+ authorID string,
+ topicID uint64,
+) (uint64, error) {
+ post := &models.Post{
+ Text: text,
+ AuthorID: authorID,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+
+ return post.ID, t.db.Update(func(tx *bbolt.Tx) error {
+ bucket, err := tx.CreateBucketIfNotExists([]byte(fmt.Sprintf("posts_%d", topicID)))
+ if err != nil {
+ return err
+ }
+ id, err := bucket.NextSequence()
+ if err != nil {
+ return err
+ }
+ post.ID = id
+ pb, err := json.Marshal(post)
+ if err != nil {
+ return err
+ }
+
+ return bucket.Put([]byte(strconv.Itoa(int(id))), pb)
+ })
+}
+
+func (t *Post) List(topicID uint64) ([]*models.Post, error) {
+ posts := make([]*models.Post, 0)
+
+ return posts, t.db.View(func(tx *bbolt.Tx) error {
+ return tx.Bucket([]byte(fmt.Sprintf("posts_%d", topicID))).ForEach(func(k, v []byte) error {
+ post := new(models.Post)
+
+ if err := json.Unmarshal(v, post); err != nil {
+ return err
+ }
+
+ posts = append(posts, post)
+
+ return nil
+ })
+ })
+}
diff --git a/repository/topic.go b/repository/topic.go
new file mode 100644
index 0000000..4c34643
--- /dev/null
+++ b/repository/topic.go
@@ -0,0 +1,101 @@
+package repository
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+ "time"
+
+ "gitrepo.ru/neonxp/gorum/models"
+ "go.etcd.io/bbolt"
+)
+
+type Topic struct {
+ db *bbolt.DB
+}
+
+func NewTopic(db *bbolt.DB) *Topic {
+ return &Topic{
+ db: db,
+ }
+}
+
+func (t *Topic) Init() error {
+ return t.db.Update(func(tx *bbolt.Tx) error {
+ _, err := tx.CreateBucketIfNotExists([]byte("topics"))
+ return err
+ })
+}
+
+func (t *Topic) Create(title, text, authorID string, parentID uint64) (*models.Topic, error) {
+ topic := &models.Topic{
+ Topic: title,
+ Text: text,
+ AuthorID: authorID,
+ ParentID: parentID,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ }
+
+ return topic, t.db.Update(func(tx *bbolt.Tx) error {
+ bucket, err := tx.CreateBucketIfNotExists([]byte("topics"))
+ if err != nil {
+ return err
+ }
+ id, err := bucket.NextSequence()
+ if err != nil {
+ return err
+ }
+ topic.ID = id
+ tb, err := json.Marshal(topic)
+ if err != nil {
+ return err
+ }
+
+ if _, err := tx.CreateBucketIfNotExists([]byte(fmt.Sprintf("posts_%d", topic.ID))); err != nil {
+ return err
+ }
+
+ return bucket.Put([]byte(strconv.Itoa(int(topic.ID))), tb)
+ })
+}
+
+func (t *Topic) List(parentID uint64) ([]*models.Topic, error) {
+ topics := make([]*models.Topic, 0)
+
+ return topics, t.db.View(func(tx *bbolt.Tx) error {
+ bucket := tx.Bucket([]byte("topics"))
+ if bucket == nil {
+ return models.ErrDBNotInitialized
+ }
+
+ return bucket.ForEach(func(k, v []byte) error {
+ t := new(models.Topic)
+ if err := json.Unmarshal(v, t); err != nil {
+ return err
+ }
+ if t.ParentID == parentID {
+ topics = append(topics, t)
+ }
+
+ return nil
+ })
+ })
+}
+
+func (t *Topic) Get(topicID uint64) (*models.Topic, error) {
+ topic := new(models.Topic)
+
+ return topic, t.db.View(func(tx *bbolt.Tx) error {
+ bucket := tx.Bucket([]byte("topics"))
+ if bucket == nil {
+ return models.ErrDBNotInitialized
+ }
+ tb := bucket.Get([]byte(strconv.Itoa(int(topicID))))
+ if tb == nil {
+ return models.ErrTopicNotFound
+ }
+
+ return json.Unmarshal(tb, topic)
+ })
+}
diff --git a/repository/user.go b/repository/user.go
index 5c3cce6..b01eaee 100644
--- a/repository/user.go
+++ b/repository/user.go
@@ -1,31 +1,44 @@
package repository
import (
- "context"
+ "encoding/json"
"fmt"
- "github.com/uptrace/bun"
+ normalizer "github.com/dimuska139/go-email-normalizer/v2"
"gitrepo.ru/neonxp/gorum/models"
+ "go.etcd.io/bbolt"
"golang.org/x/crypto/bcrypt"
)
type User struct {
- db *bun.DB
+ db *bbolt.DB
}
-func NewUser(db *bun.DB) *User {
+func NewUser(db *bbolt.DB) *User {
return &User{
db: db,
}
}
-func (u *User) Create(ctx context.Context, email, password, username string, role models.UserRole) (int, error) {
+func (t *User) Init() error {
+ return t.db.Update(func(tx *bbolt.Tx) error {
+ _, err := tx.CreateBucketIfNotExists([]byte("users"))
+ return err
+ })
+}
+func (u *User) Create(email, password, username string, role models.UserRole) error {
+
+ if len(password) < 8 {
+ return models.ErrInvalidPassword
+ }
hpassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
- return 0, models.ErrInvalidPassword
+ return models.ErrInvalidPassword
}
+ email = normalizer.NewNormalizer().Normalize(email)
+
user := &models.User{
Email: email,
Password: string(hpassword),
@@ -33,29 +46,74 @@ func (u *User) Create(ctx context.Context, email, password, username string, rol
Role: role,
}
- if _, err := u.db.NewInsert().Model(user).Returning("id").Exec(ctx); err != nil {
- return 0, models.ErrUserAlreadyExists
- }
+ u.db.Update(func(tx *bbolt.Tx) error {
+ bucket, err := tx.CreateBucketIfNotExists([]byte("users"))
+ if err != nil {
+ return err
+ }
+ ub := bucket.Get([]byte(email))
+ if ub != nil {
+ return models.ErrUserAlreadyExists
+ }
+
+ ub, err = json.Marshal(user)
+ if err != nil {
+ return err
+ }
+
+ return bucket.Put([]byte(email), ub)
+ })
- return user.ID, nil
+ return nil
}
-func (u *User) Login(ctx context.Context, email, password string) (*models.User, error) {
+func (u *User) Login(email, password string) (*models.User, error) {
user := new(models.User)
+ email = normalizer.NewNormalizer().Normalize(email)
- if err := u.db.NewSelect().Model(user).Where("email = ?", email).Scan(ctx); err != nil {
- return nil, fmt.Errorf("user not found: %w", models.ErrInvalidUserOrPassword)
- }
+ return user, u.db.View(func(tx *bbolt.Tx) error {
+ ub := tx.Bucket([]byte("users")).Get([]byte(email))
+ if ub == nil {
+ return models.ErrInvalidUserOrPassword
+ }
+ if err := json.Unmarshal(ub, user); err != nil {
+ return err
+ }
- if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
- return nil, fmt.Errorf("invalid password: %w", models.ErrInvalidUserOrPassword)
- }
+ if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
+ return fmt.Errorf("invalid password: %w", models.ErrInvalidUserOrPassword)
+ }
+
+ return nil
+ })
+}
+
+func (u *User) Get(email string) (*models.User, error) {
+ user := new(models.User)
+ email = normalizer.NewNormalizer().Normalize(email)
+
+ return user, u.db.View(func(tx *bbolt.Tx) error {
+ ub := tx.Bucket([]byte("users")).Get([]byte(email))
+ if ub == nil {
+ return models.ErrInvalidUserOrPassword
+ }
- return user, nil
+ return json.Unmarshal(ub, user)
+ })
}
-func (u *User) List(ctx context.Context) ([]*models.User, error) {
+func (u *User) List() ([]*models.User, error) {
ret := make([]*models.User, 0)
- return ret, u.db.NewSelect().Model(&ret).Scan(ctx)
+ return ret, u.db.View(func(tx *bbolt.Tx) error {
+ bucket := tx.Bucket([]byte("users"))
+ return bucket.ForEach(func(k, v []byte) error {
+ u := new(models.User)
+ if err := json.Unmarshal(v, u); err != nil {
+ return err
+ }
+ ret = append(ret, u)
+ return nil
+ })
+ })
}
diff --git a/routes/node.go b/routes/node.go
deleted file mode 100644
index e8aa455..0000000
--- a/routes/node.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package routes
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/labstack/echo/v4"
- "gitrepo.ru/neonxp/gorum/contextlib"
- "gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
- "gitrepo.ru/neonxp/gorum/views"
-)
-
-func (r *Router) Node(c echo.Context) error {
- sParentID := c.Param("id")
- parentID := 0
- var err error
- if sParentID != "" {
- parentID, err = strconv.Atoi(sParentID)
- if err != nil {
- return err
- }
- }
-
- node := &models.Node{
- ID: 0,
- Text: "Gorum",
- }
- if parentID > 0 {
- node, err = r.nodeRepo.Get(c.Request().Context(), parentID)
- if err != nil {
- return err
- }
- }
-
- nodes, err := r.nodeRepo.List(c.Request().Context(), parentID)
- if err != nil {
- return err
- }
-
- topics := make([]*models.Node, 0, len(nodes))
- posts := make([]*models.Node, 0, len(nodes))
- for _, n := range nodes {
- switch n.Type {
- case models.PostType:
- posts = append(posts, n)
- case models.TopicType:
- topics = append(topics, n)
- }
- }
-
- return utils.Render(c, views.Node(node, topics, posts))
-}
-
-func (r *Router) NewPost(c echo.Context) error {
- req := new(nodeRequest)
- if err := c.Bind(req); err != nil {
- return err
- }
- user := contextlib.GetUser(c.Request().Context())
- if user == nil {
- return echo.ErrForbidden
- }
- sParentID := c.Param("id")
- parentID, err := strconv.Atoi(sParentID)
- if err != nil {
- return err
- }
-
- if c.Request().Method == http.MethodPost {
- postID, err := r.nodeRepo.Create(c.Request().Context(), req.Type, req.Text, user.ID, parentID)
- if err != nil {
- return err
- }
-
- return c.Redirect(302, fmt.Sprintf("/t/%d#post%d", parentID, postID))
- }
-
- node := &models.Node{
- ID: 0,
- Text: "Gorum",
- }
- if parentID > 0 {
- node, err = r.nodeRepo.Get(c.Request().Context(), parentID)
- if err != nil {
- return err
- }
- }
-
- return utils.Render(c, views.NewPost(node))
-}
-func (r *Router) NewTopic(c echo.Context) error {
- req := new(nodeRequest)
- if err := c.Bind(req); err != nil {
- return err
- }
- user := contextlib.GetUser(c.Request().Context())
- if user == nil {
- return echo.ErrForbidden
- }
- sParentID := c.Param("id")
- parentID, err := strconv.Atoi(sParentID)
- if err != nil {
- return err
- }
-
- if c.Request().Method == http.MethodPost {
- postID, err := r.nodeRepo.Create(c.Request().Context(), req.Type, req.Text, user.ID, parentID)
- if err != nil {
- return err
- }
-
- return c.Redirect(302, fmt.Sprintf("/t/%d", postID))
- }
-
- node := &models.Node{
- ID: 0,
- Text: "Gorum",
- }
- if parentID > 0 {
- node, err = r.nodeRepo.Get(c.Request().Context(), parentID)
- if err != nil {
- return err
- }
- }
-
- return utils.Render(c, views.NewTopic(node))
-}
diff --git a/routes/routes.go b/routes/routes.go
deleted file mode 100644
index 835991a..0000000
--- a/routes/routes.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package routes
-
-import (
- "gitrepo.ru/neonxp/gorum/repository"
-)
-
-type Router struct {
- userRepo *repository.User
- nodeRepo *repository.Node
-}
-
-func NewRouter(userRepo *repository.User, nodeRepo *repository.Node) *Router {
- return &Router{
- userRepo: userRepo,
- nodeRepo: nodeRepo,
- }
-}
diff --git a/routes/user.go b/routes/user.go
deleted file mode 100644
index dd5bd61..0000000
--- a/routes/user.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package routes
-
-import (
- "log"
- "net/http"
-
- "github.com/gorilla/sessions"
- "github.com/labstack/echo-contrib/session"
- "github.com/labstack/echo/v4"
- "gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
- "gitrepo.ru/neonxp/gorum/views"
-)
-
-func (r *Router) Login(c echo.Context) error {
- req := new(loginRequest)
- if err := c.Bind(req); err != nil {
- return err
- }
- if c.Request().Method == http.MethodPost {
- u, err := r.userRepo.Login(c.Request().Context(), req.Email, req.Password)
- if err != nil {
- return err
- }
- sess, err := session.Get("session", c)
- if err != nil {
- return err
- }
- maxAge := 0
- if req.Remember == "on" {
- maxAge = 86400 * 14
- }
- sess.Options = &sessions.Options{
- Path: "/",
- MaxAge: maxAge,
- HttpOnly: true,
- }
- sess.Values["user"] = *u
- if err := sess.Save(c.Request(), c.Response()); err != nil {
- return err
- }
-
- return c.Redirect(302, "/")
- }
-
- return utils.Render(c, views.Login(req.Email))
-}
-
-func (r *Router) Logout(c echo.Context) error {
- sess, err := session.Get("session", c)
- if err != nil {
- return err
- }
- sess.Options = &sessions.Options{
- Path: "/",
- MaxAge: -1,
- HttpOnly: true,
- }
- sess.Values["user"] = nil
- if err := sess.Save(c.Request(), c.Response()); err != nil {
- return err
- }
- if err := sess.Save(c.Request(), c.Response()); err != nil {
- return err
- }
-
- return c.Redirect(302, "/")
-}
-
-func (r *Router) Register(c echo.Context) error {
- req := new(registerRequest)
- if err := c.Bind(req); err != nil {
- return err
- }
- if c.Request().Method == http.MethodPost {
- uid, err := r.userRepo.Create(c.Request().Context(), req.Email, req.Password, req.Username, models.RoleUser)
- if err != nil {
- return err
- }
- log.Println(uid)
-
- return c.Redirect(302, "/login")
- }
-
- return utils.Render(c, views.Register(req.Username, req.Email))
-}
diff --git a/utils/date.go b/utils/date.go
deleted file mode 100644
index 3a6197d..0000000
--- a/utils/date.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package utils
-
-import "time"
-
-func FormatDate(ts int64) string {
- return time.Unix(ts, 0).Format("15:04 02.01.2006")
-}
diff --git a/utils/middleware.go b/utils/middleware.go
new file mode 100644
index 0000000..c297d65
--- /dev/null
+++ b/utils/middleware.go
@@ -0,0 +1,8 @@
+package utils
+
+import "net/http"
+
+// Middleware in itself simply takes a http.Handler as the first parameter, wraps
+// it and returns a new http.Handler for the server to call (wraps handlers with
+// closures according to the principle of Russian doll).
+type Middleware func(http.Handler) http.Handler
diff --git a/views/error.templ b/views/error.templ
index 3e857ef..27d0b55 100644
--- a/views/error.templ
+++ b/views/error.templ
@@ -1,7 +1,7 @@
package views
templ ErrorPage(err error) {
- @Layout(nil) {
+ @Layout() {
<h1>Ошибка</h1>
{ err.Error() }
}
diff --git a/views/error_templ.go b/views/error_templ.go
index 8513d1d..acf3743 100644
--- a/views/error_templ.go
+++ b/views/error_templ.go
@@ -53,7 +53,7 @@ func ErrorPage(err error) templ.Component {
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/views/layouts.templ b/views/layouts.templ
index f1c2deb..768a571 100644
--- a/views/layouts.templ
+++ b/views/layouts.templ
@@ -2,12 +2,11 @@ package views
import (
"context"
- "fmt"
"gitrepo.ru/neonxp/gorum/contextlib"
"gitrepo.ru/neonxp/gorum/models"
)
-templ Layout(parent *models.Node) {
+templ Layout() {
<!DOCTYPE html>
<html lang="ru">
<head>
@@ -24,27 +23,15 @@ templ Layout(parent *models.Node) {
<li>
<strong>Gorum BBS</strong>
</li>
- if parent != nil {
- <li>
- <a href="/">Список тем</a>
- </li>
- <li>
- switch parent.Type {
- case models.PostType:
- <a href={ templ.URL(fmt.Sprintf("/p/%d", parent.ID)) }>На уровень выше</a>
- case models.TopicType:
- <a href={ templ.URL(fmt.Sprintf("/t/%d", parent.ID)) }>К предыдущей теме</a>
- }
- </li>
- } else {
- <li>
- <a href="/">Список тем</a>
- </li>
+ <li>
+ <a href="/">Список тем</a>
+ </li>
+ if isAuthorized(ctx) {
+ <li><a href="/t/new">Новая тема</a></li>
}
</ul>
<ul>
if isAuthorized(ctx) {
- // <li><a href="/topic/new">Новая тема</a></li>
<li>{ getUser(ctx).Username }</li>
<li><form action="/logout" method="POST"><input type="submit" value="Выход"/></form></li>
} else {
@@ -53,7 +40,7 @@ templ Layout(parent *models.Node) {
}
</ul>
</nav>
- <main class="container">
+ <main class="container-fluid">
{ children... }
</main>
<footer class="container-fluid">
diff --git a/views/layouts_templ.go b/views/layouts_templ.go
index 6191925..4f563a5 100644
--- a/views/layouts_templ.go
+++ b/views/layouts_templ.go
@@ -10,12 +10,11 @@ import templruntime "github.com/a-h/templ/runtime"
import (
"context"
- "fmt"
"gitrepo.ru/neonxp/gorum/contextlib"
"gitrepo.ru/neonxp/gorum/models"
)
-func Layout(parent *models.Node) templ.Component {
+func Layout() 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
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -40,57 +39,18 @@ func Layout(parent *models.Node) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/css/pico." + ctx.Value(contextlib.ThemeKey).(string) + ".min.css")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 17, Col: 107}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 16, Col: 107}
}
_, 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("\"><link rel=\"stylesheet\" href=\"/assets/css/style.css\"><title>Gorum</title></head><body><nav class=\"container-fluid\"><ul><li><strong>Gorum BBS</strong></li>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><link rel=\"stylesheet\" href=\"/assets/css/style.css\"><title>Gorum</title></head><body><nav class=\"container-fluid\"><ul><li><strong>Gorum BBS</strong></li><li><a href=\"/\">Список тем</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- if parent != nil {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/\">Список тем</a></li><li>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- switch parent.Type {
- case models.PostType:
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d", parent.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">На уровень выше</a>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- case models.TopicType:
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d", parent.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">К предыдущей теме</a>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/\">Список тем</a></li>")
+ if isAuthorized(ctx) {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/t/new\">Новая тема</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -100,16 +60,16 @@ func Layout(parent *models.Node) templ.Component {
return templ_7745c5c3_Err
}
if isAuthorized(ctx) {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <li>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var5 string
- templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(getUser(ctx).Username)
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(getUser(ctx).Username)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 48, Col: 33}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 35, Col: 33}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -123,7 +83,7 @@ func Layout(parent *models.Node) templ.Component {
return templ_7745c5c3_Err
}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></nav><main class=\"container\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></nav><main class=\"container-fluid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/views/login.templ b/views/login.templ
index 6c897c6..7f3a385 100644
--- a/views/login.templ
+++ b/views/login.templ
@@ -1,7 +1,7 @@
package views
templ Login(email string) {
- @Layout(nil) {
+ @Layout() {
<h1>Вход</h1>
<form method="post">
<label for="email">Электропочта:</label>
diff --git a/views/login_templ.go b/views/login_templ.go
index 034133f..2ae400f 100644
--- a/views/login_templ.go
+++ b/views/login_templ.go
@@ -57,7 +57,7 @@ func Login(email string) templ.Component {
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/views/new.templ b/views/new.templ
index c83a4a9..ef84d74 100644
--- a/views/new.templ
+++ b/views/new.templ
@@ -1,51 +1,54 @@
package views
import (
- "fmt"
"gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
"strconv"
)
-templ NewPost(parent *models.Node) {
- @Layout(parent.Parent) {
- <article>
- <header class="post-header">
- <span>
- { parent.Author.Username }
- </span>
- <span>
- { utils.FormatDate(parent.CreatedAt) }
- </span>
- </header>
- @templ.Raw(utils.MarkdownToHTML(parent.Text))
- </article>
- @NewPostForm(parent)
+templ NewPost(parent *models.Topic) {
+ @Layout() {
+ // <article>
+ // <header class="post-header">
+ // <span>
+ // { parent.Author.Username }
+ // </span>
+ // <span>
+ // { utils.FormatDate(parent.CreatedAt) }
+ // </span>
+ // </header>
+ // @templ.Raw(utils.MarkdownToHTML(parent.Text))
+ // </article>
+ if parent != nil {
+ @NewPostForm(parent.ID)
+ }
}
}
-templ NewTopic(parent *models.Node) {
- @Layout(parent.Parent) {
- <h1>{parent.Text}</h1>
- @NewTopicForm(parent)
+
+templ NewTopic(parent *models.Topic) {
+ @Layout() {
+ <h1>{ parent.Topic }</h1>
+ @NewTopicForm(parent.ID)
}
}
-templ NewPostForm(parent *models.Node) {
- <form method="post" action={ templ.URL(fmt.Sprintf("/p/%d/new", parent.ID)) }>
+templ NewPostForm(parentID uint64) {
+ <form method="post" action="/p/new">
@CSRF()
- <input type="hidden" name="type" value={ strconv.Itoa(int(models.PostType)) }/>
+ <input type="hidden" name="parent" value={ strconv.Itoa(int(parentID)) }/>
<label for="text"><strong>Ответ</strong></label>
<textarea name="text" id="text" placeholder="текст..." rows="5"></textarea>
<input type="submit" value="Создать"/>
</form>
}
-templ NewTopicForm(parent *models.Node) {
- <form method="post" action={ templ.URL(fmt.Sprintf("/t/%d/new", parent.ID)) }>
+templ NewTopicForm(parentID uint64) {
+ <form method="post" action="/t/new">
@CSRF()
- <input type="hidden" name="type" value={ strconv.Itoa(int(models.TopicType)) }/>
- <label for="text"><strong>Новая тема</strong></label>
- <input type="text" name="text" id="text" placeholder="название темы..." />
+ <input type="hidden" name="parent" value={ strconv.Itoa(int(parentID)) }/>
+ <label for="topic"><strong>Новая тема</strong></label>
+ <input type="text" name="topic" id="topic" placeholder="название темы..."/>
+ <label for="text"><strong>Описание</strong></label>
+ <textarea name="text" id="text" placeholder="текст..." rows="5"></textarea>
<input type="submit" value="Создать"/>
</form>
}
diff --git a/views/new_templ.go b/views/new_templ.go
index 53f60ed..677b7c6 100644
--- a/views/new_templ.go
+++ b/views/new_templ.go
@@ -9,13 +9,11 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
- "fmt"
"gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
"strconv"
)
-func NewPost(parent *models.Node) templ.Component {
+func NewPost(parent *models.Topic) 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
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -45,51 +43,19 @@ func NewPost(parent *models.Node) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header class=\"post-header\"><span>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(parent.Author.Username)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 15, Col: 29}
- }
- _, 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("</span> <span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var4 string
- templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(parent.CreatedAt))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 18, Col: 41}
- }
- _, 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("</span></header>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templ.Raw(utils.MarkdownToHTML(parent.Text)).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = NewPostForm(parent).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
+ if parent != nil {
+ templ_7745c5c3_Err = NewPostForm(parent.ID).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = Layout(parent.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -97,7 +63,7 @@ func NewPost(parent *models.Node) templ.Component {
})
}
-func NewTopic(parent *models.Node) templ.Component {
+func NewTopic(parent *models.Topic) 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
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -110,12 +76,12 @@ func NewTopic(parent *models.Node) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var5 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var5 == nil {
- templ_7745c5c3_Var5 = templ.NopComponent
+ templ_7745c5c3_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var4 := 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 {
@@ -131,12 +97,12 @@ func NewTopic(parent *models.Node) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var7 string
- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(parent.Text)
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(parent.Topic)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 28, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 29, Col: 20}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -144,13 +110,13 @@ func NewTopic(parent *models.Node) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = NewTopicForm(parent).Render(ctx, templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = NewTopicForm(parent.ID).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = Layout(parent.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -158,7 +124,7 @@ func NewTopic(parent *models.Node) templ.Component {
})
}
-func NewPostForm(parent *models.Node) templ.Component {
+func NewPostForm(parentID uint64) 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
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -171,21 +137,12 @@ func NewPostForm(parent *models.Node) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var8 == nil {
- templ_7745c5c3_Var8 = templ.NopComponent
+ templ_7745c5c3_Var6 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var6 == nil {
+ templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var9 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d/new", parent.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"/p/new\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -193,16 +150,16 @@ func NewPostForm(parent *models.Node) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"type\" value=\"")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"parent\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var10 string
- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.PostType)))
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(parentID)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 36, Col: 77}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 37, Col: 72}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -214,7 +171,7 @@ func NewPostForm(parent *models.Node) templ.Component {
})
}
-func NewTopicForm(parent *models.Node) templ.Component {
+func NewTopicForm(parentID uint64) 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
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
@@ -227,21 +184,12 @@ func NewTopicForm(parent *models.Node) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var11 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var11 == nil {
- templ_7745c5c3_Var11 = templ.NopComponent
+ templ_7745c5c3_Var8 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var8 == nil {
+ templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d/new", parent.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"/t/new\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -249,20 +197,20 @@ func NewTopicForm(parent *models.Node) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"type\" value=\"")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"parent\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var13 string
- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.TopicType)))
+ var templ_7745c5c3_Var9 string
+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(parentID)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 46, Col: 78}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 47, Col: 72}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"text\"><strong>Новая тема</strong></label> <input type=\"text\" name=\"text\" id=\"text\" placeholder=\"название темы...\"> <input type=\"submit\" value=\"Создать\"></form>")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"topic\"><strong>Новая тема</strong></label> <input type=\"text\" name=\"topic\" id=\"topic\" placeholder=\"название темы...\"> <label for=\"text\"><strong>Описание</strong></label> <textarea name=\"text\" id=\"text\" placeholder=\"текст...\" rows=\"5\"></textarea> <input type=\"submit\" value=\"Создать\"></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/views/nodes.templ b/views/nodes.templ
deleted file mode 100644
index e6a120c..0000000
--- a/views/nodes.templ
+++ /dev/null
@@ -1,99 +0,0 @@
-package views
-
-import (
- "fmt"
- "gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
- "strconv"
-)
-
-templ Node(node *models.Node, topics []*models.Node, nodes []*models.Node) {
- @Layout(node.Parent) {
- switch node.Type {
- case models.TopicType:
- <h1>{ node.Text }</h1>
- <div>
- <a href={ templ.URL(fmt.Sprintf("/t/%d/new", node.ID)) }>Новая подтема</a>
- </div>
- case models.PostType:
- <h1>Пост</h1>
- @Post(node, 0, false)
- }
- if len(topics) != 0 {
- <table>
- <thead>
- <tr>
- <th>Тема</th>
- <th>Тем/Ответов</th>
- <th>Дата</th>
- <th>Автор</th>
- </tr>
- </thead>
- <tbody>
- for _, n := range topics {
- @Topic(n)
- }
- </tbody>
- </table>
- }
- if len(nodes) == 0 {
- <strong>Постов нет</strong>
- }
- for _, n := range nodes {
- @Post(n, level(node), true)
- }
- if isAuthorized(ctx) {
- @NewPostForm(node)
- } else {
- <a href="/login">Войдите</a> чтобы ответить в тему.
- }
- }
-}
-
-templ Topic(n *models.Node) {
- <tr>
- <td>
- <a href={ templ.URL(fmt.Sprintf("/t/%d", n.ID)) }>{ n.Text }</a>
- </td>
- <td>
- { strconv.Itoa(len(n.Children)) }
- </td>
- <td>
- { utils.FormatDate(n.CreatedAt) }
- </td>
- <td>
- { n.Author.Username }
- </td>
- </tr>
-}
-
-css levelStyle(level int) {
- margin-left: { fmt.Sprintf("%dem", level) };
-}
-
-templ Post(n *models.Node, level int, withFooter bool) {
- <article id={ fmt.Sprintf("post%d", n.ID) } class={ levelStyle(level) }>
- <header class="post-header">
- <span>
- { n.Author.Username }
- </span>
- <span>
- { utils.FormatDate(n.CreatedAt) }
- </span>
- </header>
- @templ.Raw(utils.MarkdownToHTML(n.Text))
- if withFooter {
- <footer class="post-header">
- <a href={ templ.URL(fmt.Sprintf("/p/%d/new", n.ID)) }>Ответить</a>
- <a href={ templ.URL(fmt.Sprintf("/p/%d", n.ID)) }>Ответов: { strconv.Itoa(len(n.Children)) }</a>
- </footer>
- }
- </article>
-}
-
-func level(node *models.Node) int {
- if node.Type == models.PostType {
- return 1
- }
- return 0
-}
diff --git a/views/nodes_templ.go b/views/nodes_templ.go
deleted file mode 100644
index fc23258..0000000
--- a/views/nodes_templ.go
+++ /dev/null
@@ -1,377 +0,0 @@
-// Code generated by templ - DO NOT EDIT.
-
-// templ: version: v0.2.747
-package views
-
-//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"
- "gitrepo.ru/neonxp/gorum/models"
- "gitrepo.ru/neonxp/gorum/utils"
- "strconv"
-)
-
-func Node(node *models.Node, topics []*models.Node, nodes []*models.Node) 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
- 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_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)
- switch node.Type {
- case models.TopicType:
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(node.Text)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 14, Col: 19}
- }
- _, 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("</h1><div><a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d/new", node.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Новая подтема</a></div>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- case models.PostType:
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Пост</h1>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = Post(node, 0, false).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if len(topics) != 0 {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table><thead><tr><th>Тема</th><th>Тем/Ответов</th><th>Дата</th><th>Автор</th></tr></thead> <tbody>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- for _, n := range topics {
- templ_7745c5c3_Err = Topic(n).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if len(nodes) == 0 {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<strong>Постов нет</strong> ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- for _, n := range nodes {
- templ_7745c5c3_Err = Post(n, level(node), true).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if isAuthorized(ctx) {
- templ_7745c5c3_Err = NewPostForm(node).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- } else {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"/login\">Войдите</a> чтобы ответить в тему.")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- return templ_7745c5c3_Err
- })
- templ_7745c5c3_Err = Layout(node.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return templ_7745c5c3_Err
- })
-}
-
-func Topic(n *models.Node) 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
- 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_Var5 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var5 == nil {
- templ_7745c5c3_Var5 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td><a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d", n.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var7 string
- templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(n.Text)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 56, Col: 61}
- }
- _, 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("</a></td><td>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var8 string
- templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(n.Children)))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 59, Col: 34}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var9 string
- templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 62, Col: 34}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var10 string
- templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 65, Col: 22}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return templ_7745c5c3_Err
- })
-}
-
-func levelStyle(level int) templ.CSSClass {
- templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
- templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`margin-left`, fmt.Sprintf("%dem", level))))
- templ_7745c5c3_CSSID := templ.CSSID(`levelStyle`, templ_7745c5c3_CSSBuilder.String())
- return templ.ComponentCSSClass{
- ID: templ_7745c5c3_CSSID,
- Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
- }
-}
-
-func Post(n *models.Node, level int, withFooter bool) 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
- 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_Var11 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var11 == nil {
- templ_7745c5c3_Var11 = templ.NopComponent
- }
- ctx = templ.ClearChildren(ctx)
- var templ_7745c5c3_Var12 = []any{levelStyle(level)}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article id=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var13 string
- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("post%d", n.ID))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 75, Col: 42}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var14 string
- templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 1, Col: 0}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><header class=\"post-header\"><span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var15 string
- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 78, Col: 23}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var16 string
- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 81, Col: 35}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></header>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templ.Raw(utils.MarkdownToHTML(n.Text)).Render(ctx, templ_7745c5c3_Buffer)
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- if withFooter {
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"post-header\"><a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var17 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d/new", n.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var17)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Ответить</a> <a href=\"")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var18 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d", n.ID))
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var18)))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Ответов: ")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- var templ_7745c5c3_Var19 string
- templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(n.Children)))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 88, Col: 101}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></footer>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>")
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- return templ_7745c5c3_Err
- })
-}
-
-func level(node *models.Node) int {
- if node.Type == models.PostType {
- return 1
- }
- return 0
-}
diff --git a/views/posts.templ b/views/posts.templ
new file mode 100644
index 0000000..2a89a78
--- /dev/null
+++ b/views/posts.templ
@@ -0,0 +1,72 @@
+package views
+
+import (
+ "fmt"
+ "gitrepo.ru/neonxp/gorum/models"
+ "gitrepo.ru/neonxp/gorum/utils"
+)
+
+templ Posts(topic *models.Topic, topics []*models.Topic, nodes []*models.Post) {
+ @Layout() {
+ <h1>{ topic.Topic }</h1>
+ if len(topics) != 0 {
+ <table>
+ <thead>
+ <tr>
+ <th>Тема</th>
+ <th>Тем/Ответов</th>
+ <th>Дата</th>
+ <th>Автор</th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, n := range topics {
+ @Topic(n)
+ }
+ </tbody>
+ </table>
+ }
+ if topic.Text != "" {
+ <article>
+ <header class="post-header">
+ <span>
+ if topic.Author != nil {
+ { topic.Author.Username }
+ }
+ </span>
+ <span>
+ { topic.CreatedAt.Format("15:04 02.01.2006") }
+ </span>
+ </header>
+ @templ.Raw(utils.MarkdownToHTML(topic.Text))
+ </article>
+ }
+ <hr/>
+ if len(nodes) == 0 {
+ <strong>Ответов нет</strong>
+ }
+ for _, n := range nodes {
+ @Post(n)
+ }
+ <hr/>
+ if isAuthorized(ctx) {
+ @NewPostForm(topic.ID)
+ } else {
+ <a href="/login">Войдите</a> чтобы ответить в тему.
+ }
+ }
+}
+
+templ Post(n *models.Post) {
+ <article id={ fmt.Sprintf("post%d", n.ID) }>
+ <header class="post-header">
+ <span>
+ { n.Author.Username }
+ </span>
+ <span>
+ { n.CreatedAt.Format("15:04 02.01.2006") }
+ </span>
+ </header>
+ @templ.Raw(utils.MarkdownToHTML(n.Text))
+ </article>
+}
diff --git a/views/posts_templ.go b/views/posts_templ.go
new file mode 100644
index 0000000..ca91d94
--- /dev/null
+++ b/views/posts_templ.go
@@ -0,0 +1,238 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.747
+package views
+
+//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"
+ "gitrepo.ru/neonxp/gorum/models"
+ "gitrepo.ru/neonxp/gorum/utils"
+)
+
+func Posts(topic *models.Topic, topics []*models.Topic, nodes []*models.Post) 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
+ 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_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("<h1>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(topic.Topic)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 11, Col: 19}
+ }
+ _, 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("</h1>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(topics) != 0 {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table><thead><tr><th>Тема</th><th>Тем/Ответов</th><th>Дата</th><th>Автор</th></tr></thead> <tbody>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, n := range topics {
+ templ_7745c5c3_Err = Topic(n).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if topic.Text != "" {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header class=\"post-header\"><span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if topic.Author != nil {
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(topic.Author.Username)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 34, Col: 30}
+ }
+ _, 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("</span> <span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(topic.CreatedAt.Format("15:04 02.01.2006"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 38, Col: 50}
+ }
+ _, 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("</span></header>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(utils.MarkdownToHTML(topic.Text)).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <hr>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(nodes) == 0 {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<strong>Ответов нет</strong> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ for _, n := range nodes {
+ templ_7745c5c3_Err = Post(n).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <hr>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if isAuthorized(ctx) {
+ templ_7745c5c3_Err = NewPostForm(topic.ID).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"/login\">Войдите</a> чтобы ответить в тему.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func Post(n *models.Post) 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
+ 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_Var6 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var6 == nil {
+ templ_7745c5c3_Var6 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article id=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("post%d", n.ID))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 61, Col: 42}
+ }
+ _, 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("\"><header class=\"post-header\"><span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 string
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 64, Col: 23}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 string
+ templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(n.CreatedAt.Format("15:04 02.01.2006"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/posts.templ`, Line: 67, Col: 44}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></header>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(utils.MarkdownToHTML(n.Text)).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
diff --git a/views/register.templ b/views/register.templ
index 0a4384e..9974110 100644
--- a/views/register.templ
+++ b/views/register.templ
@@ -1,7 +1,7 @@
package views
templ Register(username, email string) {
- @Layout(nil) {
+ @Layout() {
<h1>Регистрация</h1>
<form method="post">
<label for="username">Имя пользователя:</label>
diff --git a/views/register_templ.go b/views/register_templ.go
index 5008775..71a53ba 100644
--- a/views/register_templ.go
+++ b/views/register_templ.go
@@ -70,7 +70,7 @@ func Register(username, email string) templ.Component {
}
return templ_7745c5c3_Err
})
- templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/views/topics.templ b/views/topics.templ
new file mode 100644
index 0000000..52011e5
--- /dev/null
+++ b/views/topics.templ
@@ -0,0 +1,45 @@
+package views
+
+import (
+ "fmt"
+ "gitrepo.ru/neonxp/gorum/models"
+)
+
+templ Topics(topics []*models.Topic) {
+ @Layout() {
+ <table>
+ <thead>
+ <tr>
+ <th>Тема</th>
+ <th>Тем/Ответов</th>
+ <th>Дата</th>
+ <th>Автор</th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, n := range topics {
+ @Topic(n)
+ }
+ </tbody>
+ </table>
+ }
+}
+
+templ Topic(n *models.Topic) {
+ <tr>
+ <td>
+ <a href={ templ.URL(fmt.Sprintf("/t/%d", n.ID)) }>{ n.Topic }</a>
+ </td>
+ <td>
+ // { strconv.Itoa(len(n.Children)) }
+ </td>
+ <td>
+ { n.CreatedAt.Format("15:04 02.01.2006") }
+ </td>
+ <td>
+ if n.Author != nil {
+ { n.Author.Username }
+ }
+ </td>
+ </tr>
+} \ No newline at end of file
diff --git a/views/topics_templ.go b/views/topics_templ.go
new file mode 100644
index 0000000..f0f7e36
--- /dev/null
+++ b/views/topics_templ.go
@@ -0,0 +1,144 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.747
+package views
+
+//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"
+ "gitrepo.ru/neonxp/gorum/models"
+)
+
+func Topics(topics []*models.Topic) 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
+ 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_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("<table><thead><tr><th>Тема</th><th>Тем/Ответов</th><th>Дата</th><th>Автор</th></tr></thead> <tbody>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, n := range topics {
+ templ_7745c5c3_Err = Topic(n).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func Topic(n *models.Topic) 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
+ 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_Var3 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var3 == nil {
+ templ_7745c5c3_Var3 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d", n.ID))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(n.Topic)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/topics.templ`, Line: 31, Col: 62}
+ }
+ _, 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("</a></td><td></td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(n.CreatedAt.Format("15:04 02.01.2006"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/topics.templ`, Line: 37, Col: 43}
+ }
+ _, 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("</td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if n.Author != nil {
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/topics.templ`, Line: 41, Col: 23}
+ }
+ _, 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("</td></tr>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}