initial
This commit is contained in:
parent
9c0a6b21f8
commit
e9a64f3b41
69 changed files with 2157 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
|
||||
dist/
|
||||
gorum.db
|
44
.goreleaser.yaml
Normal file
44
.goreleaser.yaml
Normal file
|
@ -0,0 +1,44 @@
|
|||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
|
||||
# The lines below are called `modelines`. See `:help modeline`
|
||||
# Feel free to remove those if you don't want/need to use them.
|
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
||||
|
||||
version: 2
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
# this name template makes the OS and Arch compatible with the results of `uname`.
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
# use zip for windows archives
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
51
app/cmd/migrate.go
Normal file
51
app/cmd/migrate.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
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`,
|
||||
}
|
||||
|
||||
var migrateUp = &cobra.Command{
|
||||
Use: "up",
|
||||
Short: "Migrate up",
|
||||
Long: `Up migrations`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open db failed: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
driver, err := sqlite.WithInstance(db, &sqlite.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed create migration driver: %w", err)
|
||||
}
|
||||
sourceDriver, err := iofs.New(migrations.FS, ".")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed open migrations: %w", err)
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance("fs", sourceDriver, "sqlite3", driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open migration failed: %w", err)
|
||||
}
|
||||
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
return fmt.Errorf("do migration failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
52
app/cmd/root.go
Normal file
52
app/cmd/root.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
dbFile string
|
||||
debug bool
|
||||
rootCmd = &cobra.Command{Use: "gorum"}
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initLogger)
|
||||
cobra.OnInitialize(initConfig)
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "gorum.yaml", "config file (default is 'gorum.yaml')")
|
||||
rootCmd.PersistentFlags().StringVar(&dbFile, "db", "gorum.db", "database file (default is 'gorum.db')")
|
||||
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "verbose debug output")
|
||||
viper.BindPFlag("db", serverCmd.Flags().Lookup("db"))
|
||||
}
|
||||
|
||||
func initLogger() {
|
||||
level := slog.LevelInfo
|
||||
if debug {
|
||||
level = slog.LevelDebug
|
||||
}
|
||||
logger := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})
|
||||
slog.SetDefault(slog.New(logger))
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
slog.Warn("can't read config", slog.Any("error", err))
|
||||
}
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
134
app/cmd/serve.go
Normal file
134
app/cmd/serve.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"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"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
"gitrepo.ru/neonxp/gorum/contextlib"
|
||||
"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"
|
||||
)
|
||||
|
||||
var (
|
||||
theme string
|
||||
listen string
|
||||
sessionSecret string
|
||||
serverCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Run server",
|
||||
Long: `Run forum server`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return serve(cmd.Context())
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
serverCmd.PersistentFlags().StringVar(&theme, "theme", "default", "theme to use (default is 'default')")
|
||||
serverCmd.PersistentFlags().StringVar(&listen, "listen", ":8000", "bind address to listen (default is ':8000')")
|
||||
serverCmd.PersistentFlags().StringVar(&sessionSecret, "session_secret", "s3cr3t", "sessions secret (default is 's3cr3t')")
|
||||
viper.BindPFlag("theme", serverCmd.Flags().Lookup("theme"))
|
||||
viper.BindPFlag("listen", serverCmd.Flags().Lookup("listen"))
|
||||
viper.BindPFlag("session_secret", serverCmd.Flags().Lookup("session_secret"))
|
||||
}
|
||||
|
||||
func serve(ctx context.Context) error {
|
||||
|
||||
slog.Debug(
|
||||
"params",
|
||||
slog.String("listen", listen),
|
||||
slog.String("theme", theme),
|
||||
slog.String("session_secret", sessionSecret),
|
||||
)
|
||||
ctx = context.WithValue(ctx, contextlib.ThemeKey, theme)
|
||||
|
||||
db, err := sql.Open("sqlite3", dbFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open db failed: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
orm := bun.NewDB(db, sqlitedialect.New())
|
||||
orm.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
|
||||
userRepo := repository.NewUser(orm)
|
||||
nodeRepo := repository.NewNode(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))
|
||||
}
|
||||
|
||||
e.Server.BaseContext = func(l net.Listener) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
sessionStore, err := sqlitestore.NewSqliteStoreFromConnection(db, "sessions", "", 0, []byte(sessionSecret))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed init session store: %w", 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(),
|
||||
)
|
||||
|
||||
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.GET("/", r.Node)
|
||||
e.GET("/n/:id", r.Node)
|
||||
e.GET("/n/:id/new", r.NewPost)
|
||||
e.POST("/n/:id/new", r.NewPost)
|
||||
|
||||
e.StaticFS("/assets", assets.FS)
|
||||
|
||||
slog.InfoContext(ctx, "started gorum", slog.String("bind", listen))
|
||||
|
||||
server := http.Server{
|
||||
Addr: listen,
|
||||
Handler: e,
|
||||
ErrorLog: slog.NewLogLogger(slog.Default().Handler(), slog.LevelError),
|
||||
}
|
||||
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||
return fmt.Errorf("server failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
21
config/config.go
Normal file
21
config/config.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package config
|
||||
|
||||
import "flag"
|
||||
|
||||
type Config struct {
|
||||
Listen string
|
||||
DB string
|
||||
Theme string
|
||||
SessionSecret string
|
||||
}
|
||||
|
||||
func New() *Config {
|
||||
cfg := new(Config)
|
||||
flag.StringVar(&cfg.DB, "db", "gorum.db", "path to db file")
|
||||
flag.StringVar(&cfg.Listen, "listen", ":8000", "addr to listen")
|
||||
flag.StringVar(&cfg.Theme, "theme", "default", "color theme")
|
||||
flag.StringVar(&cfg.SessionSecret, "sesstion-secret", "s3cr3t", "session secret")
|
||||
flag.Parse()
|
||||
|
||||
return cfg
|
||||
}
|
8
contextlib/context.go
Normal file
8
contextlib/context.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package contextlib
|
||||
|
||||
type contextKey string
|
||||
|
||||
var (
|
||||
ThemeKey contextKey = "theme"
|
||||
UserKey contextKey = "user"
|
||||
)
|
21
contextlib/user.go
Normal file
21
contextlib/user.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package contextlib
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
)
|
||||
|
||||
func GetUser(ctx context.Context) *models.User {
|
||||
u := ctx.Value(UserKey)
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
user, ok := u.(models.User)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &user
|
||||
}
|
78
go.mod
Normal file
78
go.mod
Normal file
|
@ -0,0 +1,78 @@
|
|||
module gitrepo.ru/neonxp/gorum
|
||||
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.747
|
||||
github.com/gomarkdown/markdown v0.0.0-20240626202925-2eda941fd024
|
||||
github.com/gorilla/sessions v1.3.0
|
||||
github.com/labstack/echo-contrib v0.17.1
|
||||
github.com/labstack/echo/v4 v4.12.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.16.0 // 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/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // 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/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/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.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/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/uptrace/bun/extra/bundebug v1.2.1
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
207
go.sum
Normal file
207
go.sum
Normal file
|
@ -0,0 +1,207 @@
|
|||
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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
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/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.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/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/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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
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/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=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.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=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
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=
|
||||
github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 h1:IprvkIKUjEjvt4VKpcmLpbMIucjrsmUPJOSlg19+a0Q=
|
||||
github.com/uptrace/bun/dialect/sqlitedialect v1.2.1/go.mod h1:mMQf4NUpgY8bnOanxGmxNiHCdALOggS4cZ3v63a9D/o=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/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=
|
||||
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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.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/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
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=
|
9
main.go
Normal file
9
main.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitrepo.ru/neonxp/gorum/app/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
34
middleware/user.go
Normal file
34
middleware/user.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitrepo.ru/neonxp/gorum/contextlib"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
2
migrations/1_schema.down.sql
Normal file
2
migrations/1_schema.down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE "nodes";
|
||||
-- DROP TABLE "users";
|
27
migrations/1_schema.up.sql
Normal file
27
migrations/1_schema.up.sql
Normal file
|
@ -0,0 +1,27 @@
|
|||
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")
|
||||
);
|
||||
|
6
migrations/fs.go
Normal file
6
migrations/fs.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package migrations
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed *.sql
|
||||
var FS embed.FS
|
9
models/errors.go
Normal file
9
models/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package models
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrInvalidUserOrPassword = errors.New("invalid user or password")
|
||||
ErrUserAlreadyExists = errors.New("user already exists")
|
||||
ErrInvalidPassword = errors.New("invalid password")
|
||||
)
|
53
models/node.go
Normal file
53
models/node.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
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
|
||||
}
|
||||
|
||||
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 NodeType int
|
||||
|
||||
const (
|
||||
TopicType NodeType = iota
|
||||
PostType
|
||||
)
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
UserPost Permission = iota << 1
|
||||
UserTopic
|
||||
AdminPost
|
||||
AdminTopic
|
||||
)
|
21
models/user.go
Normal file
21
models/user.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(User{})
|
||||
}
|
||||
|
||||
type User struct {
|
||||
bun.BaseModel `bun:"table:users,alias:u"`
|
||||
|
||||
ID int `bun:"id,pk,autoincrement"`
|
||||
Email string
|
||||
Password string
|
||||
Username string
|
||||
Photo *string
|
||||
}
|
58
repository/node.go
Normal file
58
repository/node.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
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").
|
||||
Scan(ctx)
|
||||
}
|
||||
|
||||
func (t *Node) List(ctx context.Context, topicID int) ([]*models.Node, int, error) {
|
||||
posts := make([]*models.Node, 0)
|
||||
|
||||
count, err := t.db.NewSelect().
|
||||
Model(&posts).
|
||||
Where(`parent_id = ?`, topicID).
|
||||
Relation("Author").
|
||||
ScanAndCount(ctx)
|
||||
|
||||
return posts, count, err
|
||||
}
|
54
repository/user.go
Normal file
54
repository/user.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
func NewUser(db *bun.DB) *User {
|
||||
return &User{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) Create(ctx context.Context, email, password, username string) (int, error) {
|
||||
|
||||
hpassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0, models.ErrInvalidPassword
|
||||
}
|
||||
|
||||
user := &models.User{
|
||||
Email: email,
|
||||
Password: string(hpassword),
|
||||
Username: username,
|
||||
}
|
||||
|
||||
if _, err := u.db.NewInsert().Model(user).Returning("id").Exec(ctx); err != nil {
|
||||
return 0, models.ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
return user.ID, nil
|
||||
}
|
||||
|
||||
func (u *User) Login(ctx context.Context, email, password string) (*models.User, error) {
|
||||
user := new(models.User)
|
||||
|
||||
if err := u.db.NewSelect().Model(user).Where("email = ?", email).Scan(ctx); err != nil {
|
||||
return nil, fmt.Errorf("user not found: %w", models.ErrInvalidUserOrPassword)
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
||||
return nil, fmt.Errorf("invalid password: %w", models.ErrInvalidUserOrPassword)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
75
routes/node.go
Normal file
75
routes/node.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
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, count, err := r.nodeRepo.List(c.Request().Context(), parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.Render(c, views.Node(node, nodes, count))
|
||||
}
|
||||
|
||||
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("/n/%d#post%d", parentID, postID))
|
||||
}
|
||||
|
||||
node, err := r.nodeRepo.Get(c.Request().Context(), parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.Render(c, views.NewNode(node))
|
||||
}
|
21
routes/requests.go
Normal file
21
routes/requests.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package routes
|
||||
|
||||
import "gitrepo.ru/neonxp/gorum/models"
|
||||
|
||||
type loginRequest struct {
|
||||
Email string `form:"email"`
|
||||
Password string `form:"password"`
|
||||
Remember string `form:"remember"`
|
||||
}
|
||||
|
||||
type registerRequest struct {
|
||||
Username string `form:"username"`
|
||||
Email string `form:"email"`
|
||||
Password string `form:"password"`
|
||||
Password2 string `form:"password2"`
|
||||
}
|
||||
|
||||
type nodeRequest struct {
|
||||
Type models.NodeType `form:"type"`
|
||||
Text string `form:"text"`
|
||||
}
|
17
routes/routes.go
Normal file
17
routes/routes.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
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,
|
||||
}
|
||||
}
|
85
routes/user.go
Normal file
85
routes/user.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
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/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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println(uid)
|
||||
|
||||
return c.Redirect(302, "/login")
|
||||
}
|
||||
|
||||
return utils.Render(c, views.Register(req.Username, req.Email))
|
||||
}
|
7
utils/date.go
Normal file
7
utils/date.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
func FormatDate(ts int64) string {
|
||||
return time.Unix(ts, 0).Format("15:04 02.01.2006")
|
||||
}
|
21
utils/markdown.go
Normal file
21
utils/markdown.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
func MarkdownToHTML(md string) string {
|
||||
// create markdown parser with extensions
|
||||
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
|
||||
p := parser.NewWithExtensions(extensions)
|
||||
doc := p.Parse([]byte(md))
|
||||
|
||||
// create HTML renderer with extensions
|
||||
htmlFlags := html.CommonFlags | html.HrefTargetBlank
|
||||
opts := html.RendererOptions{Flags: htmlFlags}
|
||||
renderer := html.NewRenderer(opts)
|
||||
|
||||
return string(markdown.Render(doc, renderer))
|
||||
}
|
15
utils/render.go
Normal file
15
utils/render.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func Render(c echo.Context, cmp templ.Component) error {
|
||||
c.Response().WriteHeader(http.StatusOK)
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||
|
||||
return cmp.Render(c.Request().Context(), c.Response())
|
||||
}
|
6
views/assets/assets.go
Normal file
6
views/assets/assets.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed */*
|
||||
var FS embed.FS
|
4
views/assets/css/pico.amber.min.css
vendored
Normal file
4
views/assets/css/pico.amber.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.blue.min.css
vendored
Normal file
4
views/assets/css/pico.blue.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.colors.min.css
vendored
Normal file
4
views/assets/css/pico.colors.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.cyan.min.css
vendored
Normal file
4
views/assets/css/pico.cyan.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.default.min.css
vendored
Normal file
4
views/assets/css/pico.default.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.fuchsia.min.css
vendored
Normal file
4
views/assets/css/pico.fuchsia.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.green.min.css
vendored
Normal file
4
views/assets/css/pico.green.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.grey.min.css
vendored
Normal file
4
views/assets/css/pico.grey.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.indigo.min.css
vendored
Normal file
4
views/assets/css/pico.indigo.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.jade.min.css
vendored
Normal file
4
views/assets/css/pico.jade.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.lime.min.css
vendored
Normal file
4
views/assets/css/pico.lime.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.orange.min.css
vendored
Normal file
4
views/assets/css/pico.orange.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.pink.min.css
vendored
Normal file
4
views/assets/css/pico.pink.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.pumpkin.min.css
vendored
Normal file
4
views/assets/css/pico.pumpkin.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.purple.min.css
vendored
Normal file
4
views/assets/css/pico.purple.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.red.min.css
vendored
Normal file
4
views/assets/css/pico.red.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.sand.min.css
vendored
Normal file
4
views/assets/css/pico.sand.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.slate.min.css
vendored
Normal file
4
views/assets/css/pico.slate.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.violet.min.css
vendored
Normal file
4
views/assets/css/pico.violet.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.yellow.min.css
vendored
Normal file
4
views/assets/css/pico.yellow.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
views/assets/css/pico.zinc.min.css
vendored
Normal file
4
views/assets/css/pico.zinc.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
views/assets/css/style.css
Normal file
14
views/assets/css/style.css
Normal file
|
@ -0,0 +1,14 @@
|
|||
.post-header {
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
summary {
|
||||
text-decoration: dashed;
|
||||
font-weight: bold;
|
||||
color: var(--pico-primary);
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
border: 1px dashed var(--pico-muted-border-color);
|
||||
}
|
7
views/csrf.templ
Normal file
7
views/csrf.templ
Normal file
|
@ -0,0 +1,7 @@
|
|||
package views
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
templ CSRF() {
|
||||
<input type="hidden" name={echo.HeaderXCSRFToken} value={ ctx.Value("csrf").(string) }/>
|
||||
}
|
8
views/error.templ
Normal file
8
views/error.templ
Normal file
|
@ -0,0 +1,8 @@
|
|||
package views
|
||||
|
||||
templ ErrorPage(err error) {
|
||||
@Layout() {
|
||||
<h1>Ошибка</h1>
|
||||
{err.Error()}
|
||||
}
|
||||
}
|
62
views/error_templ.go
Normal file
62
views/error_templ.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// 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"
|
||||
|
||||
func ErrorPage(err error) 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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/error.templ`, Line: 6, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
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
|
||||
})
|
||||
}
|
1
views/error_templ.txt
Normal file
1
views/error_templ.txt
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Ошибка</h1>
|
57
views/layouts.templ
Normal file
57
views/layouts.templ
Normal file
|
@ -0,0 +1,57 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitrepo.ru/neonxp/gorum/contextlib"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
)
|
||||
|
||||
templ Layout() {
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"/>
|
||||
<link rel="stylesheet" href={ "/assets/css/pico." + ctx.Value(contextlib.ThemeKey).(string) + ".min.css" }/>
|
||||
<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>
|
||||
</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 {
|
||||
<li><a href="/login">Вход</a></li>
|
||||
<li><a href="/register">Регистрация</a></li>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="container">
|
||||
{ children... }
|
||||
</main>
|
||||
<footer class="container-fluid">
|
||||
<small>Работает на <a href="https://gorum.tech/">Gorum</a>.</small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
func isAuthorized(ctx context.Context) bool {
|
||||
_, authorized := ctx.Value(contextlib.UserKey).(models.User)
|
||||
return authorized
|
||||
}
|
||||
func getUser(ctx context.Context) models.User {
|
||||
return ctx.Value(contextlib.UserKey).(models.User)
|
||||
}
|
98
views/layouts_templ.go
Normal file
98
views/layouts_templ.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"gitrepo.ru/neonxp/gorum/contextlib"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
)
|
||||
|
||||
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)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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: 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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if isAuthorized(ctx) {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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: 33, Col: 33}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func isAuthorized(ctx context.Context) bool {
|
||||
_, authorized := ctx.Value(contextlib.UserKey).(models.User)
|
||||
return authorized
|
||||
}
|
||||
func getUser(ctx context.Context) models.User {
|
||||
return ctx.Value(contextlib.UserKey).(models.User)
|
||||
}
|
7
views/layouts_templ.txt
Normal file
7
views/layouts_templ.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!doctype html><html lang=\"ru\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"
|
||||
\"><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></ul><ul>
|
||||
<li>
|
||||
</li><li><form action=\"/logout\" method=\"POST\"><input type=\"submit\" value=\"Выход\"></form></li>
|
||||
<li><a href=\"/login\">Вход</a></li><li><a href=\"/register\">Регистрация</a></li>
|
||||
</ul></nav><main class=\"container\">
|
||||
</main><footer class=\"container-fluid\"><small>Работает на <a href=\"https://gorum.tech/\">Gorum</a>.</small></footer></body></html>
|
17
views/login.templ
Normal file
17
views/login.templ
Normal file
|
@ -0,0 +1,17 @@
|
|||
package views
|
||||
|
||||
templ Login(email string) {
|
||||
@Layout() {
|
||||
<h1>Вход</h1>
|
||||
<form method="post">
|
||||
<label for="email">Электропочта:</label>
|
||||
<input type="email" id="email" name="email" required="true" placeholder="имя@mail.ru" value={ email }/>
|
||||
<label for="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required="true" placeholder="пароль"/>
|
||||
<label for="remember">
|
||||
<input type="checkbox" id="remember" name="remember" checked="checked"/> Запомнить меня?
|
||||
</label>
|
||||
<input type="submit" value="Войти"/>
|
||||
</form>
|
||||
}
|
||||
}
|
66
views/login_templ.go
Normal file
66
views/login_templ.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
// 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"
|
||||
|
||||
func Login(email string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/login.templ`, Line: 8, Col: 105}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
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
|
||||
})
|
||||
}
|
2
views/login_templ.txt
Normal file
2
views/login_templ.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
<h1>Вход</h1><form method=\"post\"><label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\"
|
||||
\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"remember\"><input type=\"checkbox\" id=\"remember\" name=\"remember\" checked=\"checked\"> Запомнить меня?</label> <input type=\"submit\" value=\"Войти\"></form>
|
30
views/new-node.templ
Normal file
30
views/new-node.templ
Normal file
|
@ -0,0 +1,30 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
templ NewNode(parent *models.Node) {
|
||||
<details>
|
||||
<summary>Создать топик</summary>
|
||||
<form method="post" action={ templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) }>
|
||||
@CSRF()
|
||||
<input type="hidden" name="type" value={ strconv.Itoa(int(models.TopicType)) }/>
|
||||
<label for="text">Топик</label>
|
||||
<input type="text" name="text" id="text" placeholder="имя темы..."/>
|
||||
<input type="submit" value="Создать"/>
|
||||
</form>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Создать пост</summary>
|
||||
<form method="post" action={ templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) }>
|
||||
@CSRF()
|
||||
<input type="hidden" name="type" value={ strconv.Itoa(int(models.PostType)) }/>
|
||||
<label for="text">Текст</label>
|
||||
<textarea name="text" id="text" placeholder="текст..." rows="10"></textarea>
|
||||
<input type="submit" value="Создать"/>
|
||||
</form>
|
||||
</details>
|
||||
}
|
85
views/new-node_templ.go
Normal file
85
views/new-node_templ.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
// 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"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func NewNode(parent *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_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d/new", parent.ID))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.TopicType)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new-node.templ`, Line: 13, Col: 79}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d/new", 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.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.PostType)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new-node.templ`, Line: 22, Col: 78}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
5
views/new-node_templ.txt
Normal file
5
views/new-node_templ.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
<details><summary>Создать топик</summary><form method=\"post\" action=\"
|
||||
\"><input type=\"hidden\" name=\"type\" value=\"
|
||||
\"> <label for=\"text\">Топик</label> <input type=\"text\" name=\"text\" id=\"text\" placeholder=\"имя темы...\"> <input type=\"submit\" value=\"Создать\"></form></details> <details><summary>Создать пост</summary><form method=\"post\" action=\"
|
||||
\"><input type=\"hidden\" name=\"type\" value=\"
|
||||
\"> <label for=\"text\">Текст</label> <textarea name=\"text\" id=\"text\" placeholder=\"текст...\" rows=\"10\"></textarea> <input type=\"submit\" value=\"Создать\"></form></details>
|
82
views/nodes.templ
Normal file
82
views/nodes.templ
Normal file
|
@ -0,0 +1,82 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitrepo.ru/neonxp/gorum/models"
|
||||
"gitrepo.ru/neonxp/gorum/utils"
|
||||
)
|
||||
|
||||
templ Node(node *models.Node, nodes []*models.Node, count int) {
|
||||
@Layout() {
|
||||
<h1>{ node.Text }</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Тема</th>
|
||||
<th>Дата</th>
|
||||
<th>Автор</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for _, n := range nodes {
|
||||
if n.Type == models.TopicType {
|
||||
@Topic(n)
|
||||
}
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<strong>Тем нет</strong>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
if len(nodes) == 0 {
|
||||
<strong>Постов нет</strong>
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if n.Type == models.PostType {
|
||||
@Post(n)
|
||||
}
|
||||
}
|
||||
if isAuthorized(ctx) {
|
||||
@NewNode(node)
|
||||
} else {
|
||||
<a href="/login">Войдите</a> чтобы ответить в тему.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ Topic(n *models.Node) {
|
||||
<tr>
|
||||
<td>
|
||||
<a href={ templ.URL(fmt.Sprintf("/n/%d", n.ID)) }>{ n.Text }</a>
|
||||
</td>
|
||||
<td>
|
||||
{ utils.FormatDate(n.CreatedAt) }
|
||||
</td>
|
||||
<td>
|
||||
{ n.Author.Username }
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
templ Post(n *models.Node) {
|
||||
<article id={ fmt.Sprintf("post%d", n.ID) }>
|
||||
<header class="post-header">
|
||||
<span>Пост</span>
|
||||
<span>
|
||||
{ n.Author.Username }
|
||||
в
|
||||
{ utils.FormatDate(n.CreatedAt) }
|
||||
<a
|
||||
href={ templ.URL(fmt.Sprintf("/n/%d#post%d", n.ParentID, n.ID)) }
|
||||
>
|
||||
#
|
||||
</a>
|
||||
</span>
|
||||
</header>
|
||||
@templ.Raw(utils.MarkdownToHTML(n.Text))
|
||||
</article>
|
||||
}
|
275
views/nodes_templ.go
Normal file
275
views/nodes_templ.go
Normal file
|
@ -0,0 +1,275 @@
|
|||
// 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 Node(node *models.Node, nodes []*models.Node, count int) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
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: 11, Col: 17}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if n.Type == models.TopicType {
|
||||
templ_7745c5c3_Err = Topic(n).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if n.Type == models.PostType {
|
||||
templ_7745c5c3_Err = Post(n).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if isAuthorized(ctx) {
|
||||
templ_7745c5c3_Err = NewNode(node).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
|
||||
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.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_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d", n.ID))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(n.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 54, Col: 61}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 10)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, 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: 57, Col: 34}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 11)
|
||||
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/nodes.templ`, Line: 60, Col: 22}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 12)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func Post(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_Var9 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var9 == nil {
|
||||
templ_7745c5c3_Var9 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 13)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, 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: 66, Col: 42}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 14)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, 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: 70, Col: 23}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 15)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, 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: 72, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 16)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d#post%d", n.ParentID, n.ID))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var13)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 17)
|
||||
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.WriteWatchModeString(templ_7745c5c3_Buffer, 18)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
18
views/nodes_templ.txt
Normal file
18
views/nodes_templ.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
<h1>
|
||||
</h1><table><thead><tr><th>Тема</th><th>Дата</th><th>Автор</th></tr></thead> <tbody>
|
||||
<tr><td colspan=\"3\"><strong>Тем нет</strong></td></tr>
|
||||
</tbody></table>
|
||||
<strong>Постов нет</strong>
|
||||
|
||||
<a href=\"/login\">Войдите</a> чтобы ответить в тему.
|
||||
<tr><td><a href=\"
|
||||
\">
|
||||
</a></td><td>
|
||||
</td><td>
|
||||
</td></tr>
|
||||
<article id=\"
|
||||
\"><header class=\"post-header\"><span>Пост</span> <span>
|
||||
в
|
||||
<a href=\"
|
||||
\">#</a></span></header>
|
||||
</article>
|
18
views/register.templ
Normal file
18
views/register.templ
Normal file
|
@ -0,0 +1,18 @@
|
|||
package views
|
||||
|
||||
templ Register(username, email string) {
|
||||
@Layout() {
|
||||
<h1>Регистрация</h1>
|
||||
<form method="post">
|
||||
<label for="username">Имя пользователя:</label>
|
||||
<input type="text" id="username" name="username" required="true" placeholder="имя пользователя" value={ username }/>
|
||||
<label for="email">Электропочта:</label>
|
||||
<input type="email" id="email" name="email" required="true" placeholder="имя@mail.ru" value={ email }/>
|
||||
<label for="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required="true" placeholder="пароль"/>
|
||||
<label for="password2">Повторите пароль:</label>
|
||||
<input type="password" id="password2" name="password2" required="true" placeholder="повторите пароль"/>
|
||||
<input type="submit" value="Регистрация"/>
|
||||
</form>
|
||||
}
|
||||
}
|
79
views/register_templ.go
Normal file
79
views/register_templ.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
// 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"
|
||||
|
||||
func Register(username, email string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
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.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(username)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 8, Col: 130}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(email)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/register.templ`, Line: 10, Col: 105}
|
||||
}
|
||||
_, 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.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
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
|
||||
})
|
||||
}
|
3
views/register_templ.txt
Normal file
3
views/register_templ.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
<h1>Регистрация</h1><form method=\"post\"><label for=\"username\">Имя пользователя:</label> <input type=\"text\" id=\"username\" name=\"username\" required=\"true\" placeholder=\"имя пользователя\" value=\"
|
||||
\"> <label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\"
|
||||
\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"password2\">Повторите пароль:</label> <input type=\"password\" id=\"password2\" name=\"password2\" required=\"true\" placeholder=\"повторите пароль\"> <input type=\"submit\" value=\"Регистрация\"></form>
|
Loading…
Reference in a new issue