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) {