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 }