diff --git a/config.go b/config.go new file mode 100644 index 0000000..5900eb7 --- /dev/null +++ b/config.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +func initConfig() error { + viper.SetDefault("host", "[::]") + viper.SetDefault("port", 8080) + viper.SetDefault("db_path", "bin.db") + viper.SetDefault("smtp.enabled", false) + viper.SetDefault("user.registration_enabled", false) + + viper.SetConfigName("config") + viper.SetConfigType("toml") + viper.AddConfigPath("/etc/bin") + viper.AddConfigPath("$HOME/.config/bin") + viper.AddConfigPath("$XDG_CONFIG_HOME/bin") + viper.AddConfigPath(".") + + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + logrus.Warnln("No config file found.") + } else { + logrus.WithError(err).Errorln("Config file found but could not read it.") + } + } + + return nil +} diff --git a/controllers/bin.go b/controllers/bin.go new file mode 100644 index 0000000..95ece06 --- /dev/null +++ b/controllers/bin.go @@ -0,0 +1,73 @@ +package controllers + +import ( + "fmt" + "net/http" + + "git.myrkvi.com/myrkvi/bin/global" + "git.myrkvi.com/myrkvi/bin/models" + "git.myrkvi.com/myrkvi/bin/utils" + "git.myrkvi.com/myrkvi/bin/views/components" + "git.myrkvi.com/myrkvi/bin/views/pages" + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +func GetBinHandler(c echo.Context) error { + file := models.File{} + result := global.DB.Where("page_key = ?", c.Param("id")).First(&file) + if result.Error == gorm.ErrRecordNotFound { + logrus.WithError(result.Error).Errorln("no file") + } + if result.Error != nil { + logrus.WithError(result.Error).Errorln("server error") + + } + + file.AdminKey = c.QueryParam("delcode") + + return utils.RenderComponents(c, http.StatusOK, + pages.BinFull(file), + ) +} + +func DeleteBinHandler(c echo.Context) error { + code := c.Param("id") + adminCode := c.FormValue("adminKey") + + if adminCode == "" { + return utils.RenderErrorToast(c, "deletion key cannot be empty") + } + + file := models.File{} + result := global.DB.Where("page_key = ?", code).First(&file) + + if result.Error != nil { + return utils.RenderErrorToast(c, "file not found") + } + + if adminCode != file.AdminKey { + return utils.RenderErrorToast(c, "invalid deletion key") + } + + global.DB.Delete(&file) + + if file.Filename == "" { + file.Filename = "file" + } + + utils.SetHeader(c, "HX-Push", "/") + return utils.RenderComponents( + c, + http.StatusOK, + pages.IndexPartial(), + components.SwapOOB( + "beforeend:#toast", + components.ToastSuccess(fmt.Sprintf("%s deleted", file.Filename)), + ), + components.NavMenu(models.DefaultMenu, 0, true), + components.SetTitle(""), + ) + +} diff --git a/controllers/handlers_partial.go b/controllers/handlers_partial.go new file mode 100644 index 0000000..35338a1 --- /dev/null +++ b/controllers/handlers_partial.go @@ -0,0 +1,19 @@ +package controllers + +import ( + "net/http" + + "git.myrkvi.com/myrkvi/bin/utils" + "git.myrkvi.com/myrkvi/bin/views/partials" + "github.com/labstack/echo/v4" +) + +func GetPartialUploadHandler(c echo.Context) error { + c.Response().Header().Add("HX-Push", "/new") + return utils.RenderComponents(c, http.StatusOK, partials.NewFileUpload()) +} + +func GetPartialTextHandler(c echo.Context) error { + c.Response().Header().Add("HX-Push", "/new?text") + return utils.RenderComponents(c, http.StatusOK, partials.NewTextSubmit()) +} diff --git a/controllers/index.go b/controllers/index.go new file mode 100644 index 0000000..b8694f8 --- /dev/null +++ b/controllers/index.go @@ -0,0 +1,17 @@ +package controllers + +import ( + "net/http" + + "git.myrkvi.com/myrkvi/bin/utils" + "git.myrkvi.com/myrkvi/bin/views/pages" + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" +) + +func IndexHandler(c echo.Context) error { + id := c.Param("id") + logrus.Infof("id is '%s'", id) + + return utils.RenderComponents(c, http.StatusOK, pages.IndexFull()) +} diff --git a/controllers/new.go b/controllers/new.go new file mode 100644 index 0000000..5ed2c51 --- /dev/null +++ b/controllers/new.go @@ -0,0 +1,82 @@ +package controllers + +import ( + "fmt" + "io" + "mime/multipart" + "net/http" + + "git.myrkvi.com/myrkvi/bin/global" + "git.myrkvi.com/myrkvi/bin/models" + "git.myrkvi.com/myrkvi/bin/utils" + "git.myrkvi.com/myrkvi/bin/views/components" + "git.myrkvi.com/myrkvi/bin/views/pages" + "github.com/alecthomas/chroma/lexers" + "github.com/labstack/echo/v4" +) + +func GetNewHandler(c echo.Context) error { + values := c.QueryParams() + wantsText := values.Has("text") + return utils.RenderComponents(c, http.StatusOK, pages.NewFull(wantsText)) +} + +func PostNewHandler(c echo.Context) error { + file, err := c.FormFile("file") + text := c.FormValue("text") + + name := c.FormValue("name") + description := c.FormValue("description") + lang := c.FormValue("lang") + + if (file == nil || err != nil) && text == "" { + return utils.RenderErrorToast(c, "file or text must be provided") + } + + code, adminCode, err := utils.GenerateCodes() + if err != nil { + return utils.RenderErrorToast(c, "server-side error occurred") + } + + if file != nil { + text, err = getTextFromFile(file) + if name == "" { + name = file.Filename + } + if err != nil { + return utils.RenderErrorToast(c, "server-side error occurred") + } + } + // Determine language from file extension if not set. + if lang == "" { + lexer := lexers.Match(name) + if lexer != nil { + lang = lexer.Config().Name + } + } + + createdFile, err := models.CreateNewBin(global.DB, text, name, description, lang, code, adminCode) + if err != nil { + return utils.RenderErrorToast(c, "server-side error occurred") + } + + utils.SetHeader(c, "HX-Push", fmt.Sprintf("/b/%s", code)) + return utils.RenderComponents(c, http.StatusOK, + pages.BinPartial(createdFile), + components.SetTitle(file.Filename), + ) +} + +func getTextFromFile(fileHeader *multipart.FileHeader) (text string, err error) { + f, err := fileHeader.Open() + if err != nil { + return + } + b, err := io.ReadAll(f) + if err != nil { + return + } + + text = string(b) + return +} diff --git a/global/global.go b/global/global.go new file mode 100644 index 0000000..12d2f36 --- /dev/null +++ b/global/global.go @@ -0,0 +1,5 @@ +package global + +import "gorm.io/gorm" + +var DB *gorm.DB diff --git a/go.mod b/go.mod index a504e1d..f985dcb 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,9 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gohugoio/hugo v0.118.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.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/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -37,9 +39,11 @@ require ( github.com/labstack/echo/v4 v4.11.1 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -47,6 +51,12 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.16.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect github.com/tdewolff/parse/v2 v2.6.8 // indirect github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -64,6 +74,7 @@ require ( golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.25.4 // indirect modernc.org/libc v1.22.5 // indirect diff --git a/go.sum b/go.sum index 68180dc..cb611e0 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cosmtrek/air v1.45.0 h1:JwNWE61dXW2SmGDy8bB28KTX+v0KFh+xqaF4kFJav6U= github.com/cosmtrek/air v1.45.0/go.mod h1:yOz9vy7edZ75KRN9+Ofqwm3OU0wuv4Csc+ikMeZxxS8= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -172,13 +173,19 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +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/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -203,6 +210,8 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +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.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -212,6 +221,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -232,13 +243,25 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -249,6 +272,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA= github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= @@ -603,6 +628,8 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers.go b/handlers.go deleted file mode 100644 index 1468604..0000000 --- a/handlers.go +++ /dev/null @@ -1,142 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - - "github.com/alecthomas/chroma/lexers" - "github.com/labstack/echo/v4" - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -func indexHandler(c echo.Context) error { - id := c.Param("id") - logrus.Infof("id is '%s'", id) - - return RenderComponent(c, http.StatusOK, indexFull()) -} - -// ░█▀█░█▀▀░█░█░░░░░█░░░█▀▀░█░█░█▀▄░█▄█░▀█▀░▀█▀░░░█▀█░█▀█░█▀▀░█▀▀ -// ░█░█░█▀▀░█▄█░░░▄▀░░░░▀▀█░█░█░█▀▄░█░█░░█░░░█░░░░█▀▀░█▀█░█░█░█▀▀ -// ░▀░▀░▀▀▀░▀░▀░░░▀░░░░░▀▀▀░▀▀▀░▀▀░░▀░▀░▀▀▀░░▀░░░░▀░░░▀░▀░▀▀▀░▀▀▀ - -func getNewHandler(c echo.Context) error { - values := c.QueryParams() - wantsText := values.Has("text") - return RenderComponent(c, http.StatusOK, newFull(wantsText)) -} - -func postNewHandler(c echo.Context) error { - file, err := c.FormFile("file") - text := c.FormValue("text") - wantsText := c.FormValue("wantsText") == "true" - - name := c.FormValue("name") - description := c.FormValue("description") - lang := c.FormValue("lang") - - if (file == nil || err != nil) && text == "" { - /* return RenderComponent(c, http.StatusOK, - CombineTempls( - newPartial(wantsText), - ErrorMessage("file or text must be provided", "error-span"), - ), - ) */ - return RenderErrorToast(c, "File or text must be provided.") - } - - code, adminCode, err := generateCodes() - if err != nil { - return RenderComponent(c, http.StatusOK, - CombineTempls( - newPartial(wantsText), - ErrorMessage("server-side error occurred", "error-span"), - ), - ) - } - - if file != nil { - text, err = getTextFromFile(file) - if name == "" { - name = file.Filename - } - if err != nil { - return RenderComponent(c, http.StatusOK, - CombineTempls( - newPartial(wantsText), - ErrorMessage("server-side error occurred", "error-span"), - ), - ) - } - } - // Determine language from file extension if not set. - if lang == "" { - lexer := lexers.Match(name) - if lexer != nil { - lang = lexer.Config().Name - } - } - - createdFile, err := createNewBin(text, name, description, lang, code, adminCode) - if err != nil { - return RenderComponent(c, http.StatusOK, - CombineTempls( - newPartial(wantsText), - ErrorMessage("server-side error occurred", "error-span"), - ), - ) - } - - c.Response().Header().Add("HX-Push", fmt.Sprintf("/b/%s", code)) - return RenderComponent(c, http.StatusOK, - binPartial(createdFile), - ) -} - -// ░█▀▄░▀█▀░█▀█░░░░░█░░░█▀▀░▀█▀░█░░░█▀▀░█▀▀ -// ░█▀▄░░█░░█░█░░░▄▀░░░░█▀▀░░█░░█░░░█▀▀░▀▀█ -// ░▀▀░░▀▀▀░▀░▀░░░▀░░░░░▀░░░▀▀▀░▀▀▀░▀▀▀░▀▀▀ -// Individual pages of uploaded files. - -func getBinHandler(c echo.Context) error { - file := File{} - result := db.Where("page_key = ?", c.Param("id")).First(&file) - if result.Error == gorm.ErrRecordNotFound { - logrus.WithError(result.Error).Errorln("no file") - } - if result.Error != nil { - logrus.WithError(result.Error).Errorln("server error") - - } - - file.AdminKey = c.QueryParam("delcode") - - return RenderComponent(c, http.StatusOK, - binFull(file), - ) -} - -func deleteBinHandler(c echo.Context) error { - code := c.Param("id") - adminCode := c.FormValue("adminKey") - - if adminCode == "" { - return RenderErrorToast(c, "deletion key cannot be empty") - } - - file := File{} - result := db.Where("page_key = ?", code).First(&file) - - if result.Error != nil { - return RenderErrorToast(c, "file not found") - } - - if adminCode != file.AdminKey { - return RenderErrorToast(c, "invalid deletion key") - } - - db.Delete(&file) - - return c.HTML(http.StatusOK, "

file deleted!

") -} diff --git a/handlers_partial.go b/handlers_partial.go deleted file mode 100644 index b27e3c7..0000000 --- a/handlers_partial.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/labstack/echo/v4" -) - -func getPartialUploadHandler(c echo.Context) error { - c.Response().Header().Add("HX-Push", "/new") - return RenderComponent(c, http.StatusOK, newFileUpload()) -} - -func getPartialTextHandler(c echo.Context) error { - c.Response().Header().Add("HX-Push", "/new?text") - return RenderComponent(c, http.StatusOK, newTextSubmit()) -} diff --git a/main.go b/main.go index 821d808..d4c8bf0 100644 --- a/main.go +++ b/main.go @@ -1,34 +1,31 @@ package main import ( - "io" - "mime/multipart" + "fmt" + "git.myrkvi.com/myrkvi/bin/controllers" + "git.myrkvi.com/myrkvi/bin/global" + "git.myrkvi.com/myrkvi/bin/models" "github.com/glebarez/sqlite" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/sirupsen/logrus" + "github.com/spf13/viper" prefixed "github.com/x-cray/logrus-prefixed-formatter" "gorm.io/gorm" ) -type MenuItem struct { - label string - href string -} - -var db *gorm.DB - func main() { logrus.SetLevel(logrus.DebugLevel) logrus.SetFormatter(&prefixed.TextFormatter{ FullTimestamp: true, }) logrus.Infoln("Application started.") + initConfig() db_string := "bin.db" var err error - db, err = gorm.Open(sqlite.Open(db_string), &gorm.Config{}) + global.DB, err = gorm.Open(sqlite.Open(db_string), &gorm.Config{}) if err != nil { logrus.WithError(err).Fatalln("Could not open database connection.") } @@ -36,7 +33,7 @@ func main() { "db connection": db_string, }).Infoln("Connected to database.") - databaseMigrations(db) + models.DatabaseMigrations(global.DB) e := echo.New() e.Debug = false @@ -58,29 +55,21 @@ func main() { e.Static("/static", "static") - e.GET("/", indexHandler) - e.GET("/new", getNewHandler) - e.POST("/new", postNewHandler) - e.GET("/b/:id", getBinHandler) - e.POST("/b/:id/delete", deleteBinHandler) + e.GET("/", controllers.IndexHandler) + e.GET("/new", controllers.GetNewHandler) + e.POST("/new", controllers.PostNewHandler) + e.GET("/b/:id", controllers.GetBinHandler) + e.POST("/b/:id/delete", controllers.DeleteBinHandler) partial := e.Group("/partial") new := partial.Group("/new") - new.GET("/upload", getPartialUploadHandler) - new.GET("/text", getPartialTextHandler) - e.Logger.Fatal(e.Start(":8080")) -} + new.GET("/upload", controllers.GetPartialUploadHandler) + new.GET("/text", controllers.GetPartialTextHandler) -func getTextFromFile(fileHeader *multipart.FileHeader) (text string, err error) { - f, err := fileHeader.Open() - if err != nil { - return - } - b, err := io.ReadAll(f) - if err != nil { - return - } - - text = string(b) - return + listenAt := fmt.Sprintf( + "%s:%d", + viper.GetString("host"), + viper.GetUint16("port"), + ) + e.Logger.Fatal(e.Start(listenAt)) } diff --git a/models.go b/models.go deleted file mode 100644 index cf0a1f1..0000000 --- a/models.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "github.com/sirupsen/logrus" - "gorm.io/gorm" -) - -type File struct { - gorm.Model - - PageKey string `gorm:"index:idx_pagekey,unique"` - AdminKey string `gorm:"index:idx_adminkey"` - - // The "virtual" filename. - Filename string - - // The description of the file submission. - Description string - - Language string - - // The contents of the file must be valid text. - Data string -} - -func databaseMigrations(db *gorm.DB) { - logrus.Infoln("Running database migrations.") - err := db.AutoMigrate(&File{}) - if err != nil { - logrus.WithError(err).Fatalln("Failed to run database migration.") - } - logrus.Infoln("Migrations ran successfully.") -} - -func createNewBin(text, name, description, language, key, adminKey string) (File, error) { - bin := File{ - Filename: name, - Description: description, - Language: language, - PageKey: key, - AdminKey: adminKey, - Data: text, - } - result := db.Create(&bin) - if result.Error != nil { - return File{}, result.Error - } - return bin, nil -} diff --git a/models/menuitem.go b/models/menuitem.go new file mode 100644 index 0000000..a3944d6 --- /dev/null +++ b/models/menuitem.go @@ -0,0 +1,21 @@ +package models + +type MenuItem struct { + Label string + Href string +} + +var DefaultMenu []MenuItem = []MenuItem{ + { + Label: "home", + Href: "/", + }, + { + Label: "new", + Href: "/new", + }, + { + Label: "about", + Href: "/about", + }, +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..72afabd --- /dev/null +++ b/models/models.go @@ -0,0 +1,80 @@ +package models + +import ( + "reflect" + + "github.com/sirupsen/logrus" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type File struct { + gorm.Model + + PageKey string `gorm:"index:idx_pagekey,unique;unique"` + AdminKey string `gorm:"index:idx_adminkey"` + + // The "virtual" filename. + Filename string + + // The description of the file submission. + Description string + + Language string + + // The contents of the file must be valid text. + Data string + + UserID uint + User User +} + +type User struct { + gorm.Model + + DisplayName string + Email string `gorm:"unique"` + Salt string + HashedPassword []byte +} + +func (u *User) MatchesPassword(password string) bool { + combined := append([]byte(u.Salt), []byte(password)...) + + return bcrypt.CompareHashAndPassword(u.HashedPassword, []byte(combined)) == nil +} + +func DatabaseMigrations(db *gorm.DB) { + tables := []interface{}{ + File{}, + User{}, + } + + logrus.Infoln("Running database migrations.") + for _, table := range tables { + name := reflect.TypeOf(table).Name() + err := db.AutoMigrate(&table) + if err != nil { + logrus.WithError(err).WithField("table", name).Fatalln("Migration failed.") + } else { + logrus.WithField("table", name).Infoln("Migration successful.") + } + } + logrus.Infoln("Migrations ran successfully.") +} + +func CreateNewBin(db *gorm.DB, text, name, description, language, key, adminKey string) (File, error) { + bin := File{ + Filename: name, + Description: description, + Language: language, + PageKey: key, + AdminKey: adminKey, + Data: text, + } + result := db.Create(&bin) + if result.Error != nil { + return File{}, result.Error + } + return bin, nil +} diff --git a/models/models_test.go b/models/models_test.go new file mode 100644 index 0000000..a6dfe45 --- /dev/null +++ b/models/models_test.go @@ -0,0 +1,30 @@ +package models + +import ( + "testing" + + "golang.org/x/crypto/bcrypt" +) + +func TestHashPassword(t *testing.T) { + salt := "BlahHa!?591" + password := "MyPassword123" + + combined := append([]byte(salt), []byte(password)...) + hashed, err := bcrypt.GenerateFromPassword(combined, 10) + if err != nil { + t.Fatalf("Failed to generate password: %v\n", err) + } + + user := User{ + Salt: salt, + HashedPassword: hashed[:], + } + + matches := user.MatchesPassword(password) + if !matches { + t.Fatal("Password does not match") + } else { + t.Log("Password matches") + } +} diff --git a/static/tailwind.css b/static/tailwind.css index 07db6ce..42bebef 100644 --- a/static/tailwind.css +++ b/static/tailwind.css @@ -642,8 +642,8 @@ video { margin-right: 1rem; } -.mt-12 { - margin-top: 3rem; +.mt-4 { + margin-top: 1rem; } .block { @@ -775,8 +775,12 @@ video { border-bottom-width: 4px; } -.border-t-4 { - border-top-width: 4px; +.border-r-4 { + border-right-width: 4px; +} + +.border-t-0 { + border-top-width: 0px; } .border-amber-300 { @@ -789,6 +793,11 @@ video { border-color: rgb(0 0 0 / var(--tw-border-opacity)); } +.border-emerald-600 { + --tw-border-opacity: 1; + border-color: rgb(5 150 105 / var(--tw-border-opacity)); +} + .border-rose-600 { --tw-border-opacity: 1; border-color: rgb(225 29 72 / var(--tw-border-opacity)); @@ -799,6 +808,11 @@ video { background-color: rgb(255 251 235 / var(--tw-bg-opacity)); } +.bg-emerald-400 { + --tw-bg-opacity: 1; + background-color: rgb(52 211 153 / var(--tw-bg-opacity)); +} + .bg-rose-400 { --tw-bg-opacity: 1; background-color: rgb(251 113 133 / var(--tw-bg-opacity)); @@ -809,6 +823,11 @@ video { padding-right: 0.25rem; } +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + .px-4 { padding-left: 1rem; padding-right: 1rem; @@ -961,6 +980,15 @@ video { margin-right: 6rem; } + .sm\:my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + } + + .sm\:mt-12 { + margin-top: 3rem; + } + .sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } @@ -968,6 +996,19 @@ video { .sm\:flex-row { flex-direction: row; } + + .sm\:border-r-0 { + border-right-width: 0px; + } + + .sm\:border-t-4 { + border-top-width: 4px; + } + + .sm\:px-0 { + padding-left: 0px; + padding-right: 0px; + } } @media (min-width: 768px) { @@ -975,11 +1016,6 @@ video { grid-column: span 4 / span 4; } - .md\:mx-2 { - margin-left: 0.5rem; - margin-right: 0.5rem; - } - .md\:mx-32 { margin-left: 8rem; margin-right: 8rem; diff --git a/util.go b/utils/util.go similarity index 56% rename from util.go rename to utils/util.go index ba1de58..e72a082 100644 --- a/util.go +++ b/utils/util.go @@ -1,25 +1,35 @@ -package main +package utils import ( "context" + "git.myrkvi.com/myrkvi/bin/views/components" "github.com/a-h/templ" "github.com/labstack/echo/v4" "github.com/teris-io/shortid" ) -func RenderComponent(c echo.Context, status int, component templ.Component) error { +// func RenderComponent(c echo.Context, status int, component templ.Component) error { +// w := c.Response().Writer +// c.Response().Status = status + +// return component.Render(context.Background(), w) +// } + +func RenderComponents(c echo.Context, status int, comps ...templ.Component) error { w := c.Response().Writer c.Response().Status = status - return component.Render(context.Background(), w) + combine := components.CombineTempls(comps...) + + return combine.Render(context.Background(), w) } func RenderErrorToast(c echo.Context, message string) error { SetHeader(c, "HX-Retarget", "#toast") SetHeader(c, "HX-Reswap", "beforeend") - return RenderComponent(c, 200, ToastError(message)) + return RenderComponents(c, 200, components.ToastError(message)) } func AddHeader(c echo.Context, key, value string) { @@ -34,7 +44,7 @@ func SetContentType(c echo.Context, value string) { SetHeader(c, "Content-Type", value) } -func generateCodes() (code string, adminCode string, err error) { +func GenerateCodes() (code string, adminCode string, err error) { code, err = shortid.Generate() if err != nil { return diff --git a/components.templ b/views/components/components.templ similarity index 59% rename from components.templ rename to views/components/components.templ index 585350e..af73b5c 100644 --- a/components.templ +++ b/views/components/components.templ @@ -1,6 +1,9 @@ -package main +package components -import "github.com/alecthomas/chroma/lexers" +import ( + "github.com/alecthomas/chroma/lexers" + "git.myrkvi.com/myrkvi/bin/models" +) func getLanguages() []string { names := lexers.Names(false) @@ -9,7 +12,7 @@ func getLanguages() []string { return names } -templ chooseSyntax(name string) { +templ ChooseSyntax(name string) { } -templ fileUpload(text, name, id string) { +templ FileUpload(text, name, id string) {