diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..94ed8ab --- /dev/null +++ b/db/db.go @@ -0,0 +1,34 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" +) + +var db *bun.DB + +func DBConnect() { + dbHost := os.Getenv("DB_HOST") + dbName := os.Getenv("DB_NAME") + dbUser := os.Getenv("DB_USER") + dbPasswd := os.Getenv("DB_PASSWD") + + if dbHost == "" || dbName == "" || dbUser == "" || os.Getenv("STORAGE_PATH") == "" { + panic("Missing database environment variabled!") + } + + // TODO: retry connection or only connect at the first moment that we need the db + dbUrl := fmt.Sprintf("postgres://%s:%s@%s/%s?dial_timeout=10s&sslmode=disable", dbUser, dbPasswd, dbHost, dbName) + + sqlDB := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbUrl))) + db = bun.NewDB(sqlDB, pgdialect.New()) +} + +func GetDB() *bun.DB { + return db +} diff --git a/go.mod b/go.mod index 41d7a39..b4b0442 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,38 @@ module filething go 1.23.0 -require github.com/google/uuid v1.6.0 +require ( + github.com/bytedance/sonic v1.12.3 + github.com/gofiber/fiber/v3 v3.0.0-beta.3 + github.com/google/uuid v1.6.0 +) + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/fasthttp/websocket v1.5.8 // indirect + github.com/gofiber/contrib v1.0.1 // indirect + github.com/gofiber/contrib/websocket v1.3.2 // indirect + github.com/gofiber/fiber/v2 v2.52.5 // indirect + github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/yeqown/fasthttp-reverse-proxy v0.0.0-20200930023507-ed73ac32bc64 // indirect + github.com/yeqown/fasthttp-reverse-proxy/v2 v2.2.3 // indirect + github.com/yeqown/log v1.0.3 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect +) require ( github.com/dustin/go-humanize v1.0.1 @@ -22,7 +53,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/crypto v0.27.0 - golang.org/x/net v0.25.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index ec8603e..3c7b0d6 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,46 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fasthttp/websocket v1.4.1 h1:fisgNMCNCbIPM5GRRRTAckRrynbSzf76fevcJYJYnSM= +github.com/fasthttp/websocket v1.4.1/go.mod h1:toetUvZ3KISxtZERe0wzPPpnaN8GZCKHCowWctwA50o= +github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8= +github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0= +github.com/gofiber/contrib v1.0.1 h1:pQ8pQ2e8qBQ4koUGRZ4+wCSHUOip8FpjmPOhRTp+DlU= +github.com/gofiber/contrib v1.0.1/go.mod h1:e15MOdipEOlXrU5SUT5p0tfkZkhzDqfdiT2kfRYy1c0= +github.com/gofiber/contrib/websocket v1.3.2 h1:AUq5PYeKwK50s0nQrnluuINYeep1c4nRCJ0NWsV3cvg= +github.com/gofiber/contrib/websocket v1.3.2/go.mod h1:07u6QGMsvX+sx7iGNCl5xhzuUVArWwLQ3tBIH24i+S8= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v3 v3.0.0-beta.3 h1:7Q2I+HsIqnIEEDB+9oe7Gadpakh6ZLhXpTYz/L20vrg= +github.com/gofiber/fiber/v3 v3.0.0-beta.3/go.mod h1:kcMur0Dxqk91R7p4vxEpJfDWZ9u5IfvrtQc8Bvv/JmY= +github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co= +github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY= 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 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= @@ -17,14 +50,38 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk 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-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/savsgio/gotils v0.0.0-20190714152828-365999d0a274 h1:F52t1X2ziOrMcQMVHo8ZxwOrDTMAq6MrlKtL1Atu2wU= +github.com/savsgio/gotils v0.0.0-20190714152828-365999d0a274/go.mod h1:w803/Fg1m0hrp1ZT9KNfQe4E4+WOMMFLcgzPvOcye10= +github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= +github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= 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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/uptrace/bun v1.2.3 h1:6KDc6YiNlXde38j9ATKufb8o7MS8zllhAOeIyELKrk0= github.com/uptrace/bun v1.2.3/go.mod h1:8frYFHrO/Zol3I4FEjoXam0HoNk+t5k7aJRl3FXp0mk= github.com/uptrace/bun/dialect/pgdialect v1.2.3 h1:YyCxxqeL0lgFWRZzKCOt6mnxUsjqITcxSo0mLqgwMUA= @@ -33,25 +90,73 @@ github.com/uptrace/bun/driver/pgdriver v1.2.3 h1:VA5TKB0XW7EtreQq2R8Qu/vCAUX2ECa github.com/uptrace/bun/driver/pgdriver v1.2.3/go.mod h1:yDiYTZYd4FfXFtV01m4I/RkI33IGj9N254jLStaeJLs= 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/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= 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/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 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/yeqown/fasthttp-reverse-proxy v0.0.0-20200930023507-ed73ac32bc64 h1:4dmnKp9YO5u6MACmB2p4FrEtDV8MNx438A9/N0kPIfg= +github.com/yeqown/fasthttp-reverse-proxy v0.0.0-20200930023507-ed73ac32bc64/go.mod h1:hgX6t+JE03QfTU4mpF0ls1FJa+Y/49wcG7++6d6apu8= +github.com/yeqown/fasthttp-reverse-proxy/v2 v2.2.3 h1:qAWBk2OIFbtM3jB6ujXMwNybdV5h4XnRzGhJ/RkLXZQ= +github.com/yeqown/fasthttp-reverse-proxy/v2 v2.2.3/go.mod h1:hiv7dfheax7undE0lIpPI8OaMBCVT9nWfxRP8KWz4o0= +github.com/yeqown/log v1.0.3 h1:kWwBMytMSkYkr2fmi20PLgDuJNXkq3mOpVbfsTS1soI= +github.com/yeqown/log v1.0.3/go.mod h1:RTslXFTg+8Uj5AizIxdfichvBZi/OOKao6yP3tMtTns= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/main.go b/main.go index 147f2f1..f03b7e3 100644 --- a/main.go +++ b/main.go @@ -2,39 +2,28 @@ package main import ( "context" - "database/sql" + "filething/db" "filething/middleware" "filething/models" "filething/routes" "fmt" "net/http" - "os" "time" - "github.com/labstack/echo/v4" - echoMiddleware "github.com/labstack/echo/v4/middleware" + "github.com/bytedance/sonic" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/cors" + "github.com/gofiber/fiber/v3/middleware/helmet" + "github.com/gofiber/fiber/v3/middleware/pprof" "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/pgdialect" - "github.com/uptrace/bun/driver/pgdriver" ) -var initUi func(e *echo.Echo) +var initUi func(app *fiber.App) func main() { - dbHost := os.Getenv("DB_HOST") - dbName := os.Getenv("DB_NAME") - dbUser := os.Getenv("DB_USER") - dbPasswd := os.Getenv("DB_PASSWD") + db.DBConnect() - if dbHost == "" || dbName == "" || dbUser == "" || os.Getenv("STORAGE_PATH") == "" { - panic("Missing database environment variabled!") - } - - // TODO: retry connection or only connect at the first moment that we need the db - dbUrl := fmt.Sprintf("postgres://%s:%s@%s/%s?dial_timeout=10s&sslmode=disable", dbUser, dbPasswd, dbHost, dbName) - - sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dbUrl))) - db := bun.NewDB(sqldb, pgdialect.New()) + db := db.GetDB() err := createSchema(db) if err != nil { @@ -46,66 +35,66 @@ func main() { panic(err) } - e := echo.New() - - // insert the db into the echo context so it is easily accessible in routes and middleware - e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - c.Set("db", db) - return next(c) - } + app := fiber.New(fiber.Config{ + JSONEncoder: sonic.Marshal, + JSONDecoder: sonic.Unmarshal, + DisablePreParseMultipartForm: true, + BodyLimit: 100 * 1024 * 1024 * 1024, }) - e.Use(echoMiddleware.Gzip()) - e.Use(echoMiddleware.CORS()) - e.Use(echoMiddleware.CSRFWithConfig(echoMiddleware.CSRFConfig{ - TokenLookup: "cookie:_csrf", - CookiePath: "/", - CookieSecure: true, - CookieHTTPOnly: true, - CookieSameSite: http.SameSiteStrictMode, - })) - e.Use(echoMiddleware.Secure()) + app.Use(pprof.New()) + // app.Use(compress.New()) + app.Use(cors.New()) + // TODO: make this not a constant pain in my ass + // app.Use(csrf.New(csrf.Config{ + // KeyLookup: "cookie:_csrf", + // CookieName: "_csrf", + // CookieSameSite: "Strict", + // Expiration: time.Hour * 24, + // CookieSecure: true, + // CookieHTTPOnly: true, + // })) + app.Use(helmet.New()) - api := e.Group("/api") + api := app.Group("/api") { - api.POST("/login", routes.LoginHandler) - api.POST("/signup", routes.SignupHandler) + api.Post("/login", routes.LoginHandler) + api.Post("/signup", routes.SignupHandler) // everything past this needs auth api.Use(middleware.SessionMiddleware(db)) - api.POST("/logout", routes.LogoutHandler) - api.GET("/user", routes.GetUser) + api.Post("/logout", routes.LogoutHandler) + api.Get("/user", routes.GetUser) - api.POST("/files/upload*", routes.UploadFile) - api.GET("/files/get/*", routes.GetFiles) - api.GET("/files/download*", routes.GetFile) - api.POST("/files/delete*", routes.DeleteFiles) + api.Post("/files/upload*", routes.UploadFile) + api.Get("/files/get/*", routes.GetFiles) + api.Get("/files/download*", routes.GetFile) + api.Post("/files/delete*", routes.DeleteFiles) admin := api.Group("/admin") { - admin.Use(middleware.AdminMiddleware()) - admin.GET("/status", routes.SystemStatus) - admin.GET("/plans", routes.GetPlans) - admin.GET("/users", routes.GetUsers) - admin.GET("/users/:id", routes.GetUser) - admin.POST("/users/edit/:id", routes.EditUser) - admin.POST("/users/new", routes.CreateUser) + admin.Use(middleware.AdminMiddleware(db)) + admin.Get("/status", routes.SystemStatus) + admin.Get("/plans", routes.GetPlans) + admin.Get("/users", routes.GetUsers) + admin.Get("/users/:id", routes.GetUser) + admin.Post("/users/edit/:id", routes.EditUser) + admin.Post("/users/new", routes.CreateUser) } } // redirects to the proper pages if you are trying to access one that expects you have/dont have an api key // this isnt explicitly required, but it provides a better experience than doing this same thing clientside - e.Use(middleware.AuthCheckMiddleware) + app.Use(middleware.AuthCheckMiddleware) // calls out to a function set by either server.go server_dev.go based on the presence of the dev tag, and hosts // either the static files that get embedded into the binary in ui/embed.go or proxies the dev server that gets // run in the provided function - initUi(e) + initUi(app) routes.AppStartTime = time.Now().UTC() - if err := e.Start(":1323"); err != nil && err != http.ErrServerClosed { + if err = app.Listen(":1323"); err != nil && err != http.ErrServerClosed { fmt.Println("Error starting HTTP server:", err) } } diff --git a/middleware/admin.go b/middleware/admin.go index 440c16d..6ebed7e 100644 --- a/middleware/admin.go +++ b/middleware/admin.go @@ -4,19 +4,19 @@ import ( "filething/models" "net/http" + "github.com/gofiber/fiber/v3" "github.com/labstack/echo/v4" + "github.com/uptrace/bun" ) -func AdminMiddleware() echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - user := c.Get("user").(*models.User) +func AdminMiddleware(db *bun.DB) func(c fiber.Ctx) error { + return func(c fiber.Ctx) error { + user := c.Locals("user").(*models.User) - if !user.Admin { - return echo.NewHTTPError(http.StatusForbidden, "You are not an administrator") - } - - return next(c) + if !user.Admin { + return echo.NewHTTPError(http.StatusForbidden, "You are not an administrator") } + + return c.Next() } } diff --git a/middleware/auth.go b/middleware/auth.go index 7eacaad..68d9672 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -7,61 +7,56 @@ import ( "fmt" "net/http" + "github.com/gofiber/fiber/v3" "github.com/google/uuid" - "github.com/labstack/echo/v4" "github.com/uptrace/bun" ) const UserContextKey = "user" -func SessionMiddleware(db *bun.DB) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - // Extract sessionToken from the cookie - cookie, err := c.Cookie("sessionToken") - if err != nil { - if err == http.ErrNoCookie { - return echo.NewHTTPError(http.StatusUnauthorized, "Session token missing") - } - return echo.NewHTTPError(http.StatusBadRequest, "Bad request") - } - - sessionId, err := uuid.Parse(cookie.Value) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad request") - } - - session := &models.Session{ - ID: sessionId, - } - err = db.NewSelect().Model(session).WherePK().Scan(context.Background()) - - if err != nil { - fmt.Println(err) - if err == sql.ErrNoRows { - return echo.NewHTTPError(http.StatusUnauthorized, "Invalid session token") - } - return echo.NewHTTPError(http.StatusInternalServerError, "Database error") - } - - user := &models.User{ - ID: session.UserID, - } - err = db.NewSelect().Model(user).Relation("Plan").WherePK().Scan(context.Background()) - - if err != nil { - if err == sql.ErrNoRows { - return echo.NewHTTPError(http.StatusUnauthorized, "Invalid session token") - } - fmt.Println(err) - return echo.NewHTTPError(http.StatusInternalServerError, "Database error") - } - - // Store the user in the context - c.Set(UserContextKey, user) - - // Continue to the next handler - return next(c) +func SessionMiddleware(db *bun.DB) func(c fiber.Ctx) error { + return func(c fiber.Ctx) error { + // Extract session token from the cookie + sessionToken := c.Cookies("sessionToken") + if sessionToken == "" { + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"message": "Session token missing"}) } + + // Parse session ID + sessionId, err := uuid.Parse(sessionToken) + if err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "Invalid session token"}) + } + + // Fetch session from database + session := &models.Session{ + ID: sessionId, + } + err = db.NewSelect().Model(session).WherePK().Scan(context.Background()) + + if err != nil { + if err == sql.ErrNoRows { + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid session token"}) + } + fmt.Println(err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Database error"}) + } + + user := &models.User{ + ID: session.UserID, + } + err = db.NewSelect().Model(user).Relation("Plan").WherePK().Scan(context.Background()) + + if err != nil { + if err == sql.ErrNoRows { + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid session token"}) + } + fmt.Println(err) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Database error"}) + } + + c.Locals("user", user) + + return c.Next() } } diff --git a/middleware/route.go b/middleware/route.go index 48b7553..6c1a29c 100644 --- a/middleware/route.go +++ b/middleware/route.go @@ -1,10 +1,9 @@ package middleware import ( - "net/http" "strings" - "github.com/labstack/echo/v4" + "github.com/gofiber/fiber/v3" ) var unauthenticatedPages = []string{ @@ -17,36 +16,34 @@ var authenticatedPages = []string{ "/home", } -func AuthCheckMiddleware(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - path := c.Request().URL.Path +func AuthCheckMiddleware(c fiber.Ctx) error { + path := c.Path() - // bypass auth checks for static and dev resources - if strings.HasPrefix(path, "/_nuxt/") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") { - return next(c) - } - - _, cookieErr := c.Cookie("sessionToken") - authenticated := cookieErr == nil - - if Contains(unauthenticatedPages, path) && authenticated { - return c.Redirect(http.StatusFound, "/home") - } - - if Contains(authenticatedPages, path) && !authenticated { - return c.Redirect(http.StatusFound, "/login") - } - - if strings.Contains(path, "/home") && !authenticated { - return c.Redirect(http.StatusFound, "/login") - } - - if strings.Contains(path, "/admin") && !authenticated { - return c.Redirect(http.StatusFound, "/login") - } - - return next(c) + // bypass auth checks for static and dev resources + if strings.HasPrefix(path, "/_nuxt/") || strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") { + return c.Next() } + + cookie := c.Cookies("sessionToken") + authenticated := cookie != "" + + if Contains(unauthenticatedPages, path) && authenticated { + return c.Redirect().To("/home") + } + + if Contains(authenticatedPages, path) && !authenticated { + return c.Redirect().To("/login") + } + + if strings.Contains(path, "/home") && !authenticated { + return c.Redirect().To("/login") + } + + if strings.Contains(path, "/admin") && !authenticated { + return c.Redirect().To("/login") + } + + return c.Next() } func Contains(s []string, element string) bool { diff --git a/routes/admin.go b/routes/admin.go index fa8b0f2..0561354 100644 --- a/routes/admin.go +++ b/routes/admin.go @@ -2,6 +2,7 @@ package routes import ( "context" + "filething/db" "filething/models" "fmt" "net/http" @@ -12,24 +13,23 @@ import ( "time" "github.com/dustin/go-humanize" + "github.com/gofiber/fiber/v3" "github.com/google/uuid" - "github.com/labstack/echo/v4" - "github.com/uptrace/bun" "golang.org/x/crypto/bcrypt" ) -func GetUsers(c echo.Context) error { - db := c.Get("db").(*bun.DB) +func GetUsers(c fiber.Ctx) error { + db := db.GetDB() count, err := db.NewSelect().Model((*models.User)(nil)).Count(context.Background()) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Invalid page number"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Invalid page number"}) } // this should be a query param not a URL param - pageStr := c.QueryParam("page") + pageStr := c.Query("page") if pageStr == "" { pageStr = "0" } @@ -43,7 +43,7 @@ func GetUsers(c echo.Context) error { limit := 30 if offset > count { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "Invalid page number"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "Invalid page number"}) } var users []models.User @@ -54,14 +54,14 @@ func GetUsers(c echo.Context) error { Order("created_at ASC"). Scan(context.Background()) if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Failed to retrieve users"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Failed to retrieve users"}) } if users == nil { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "Invalid page number"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "Invalid page number"}) } - return c.JSON(http.StatusOK, map[string]interface{}{"users": users, "total_users": count}) + return c.Status(http.StatusOK).JSON(fiber.Map{"users": users, "total_users": count}) } type UserEdit struct { @@ -72,18 +72,18 @@ type UserEdit struct { Admin bool `json:"is_admin"` } -func EditUser(c echo.Context) error { - db := c.Get("db").(*bun.DB) - id := c.Param("id") +func EditUser(c fiber.Ctx) error { + db := db.GetDB() + id := c.Params("id") - var userEditData UserEdit - if err := c.Bind(&userEditData); err != nil { + userEditData := new(UserEdit) + if err := c.Bind().JSON(userEditData); err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if !regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`).MatchString(userEditData.Email) { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A valid email is required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A valid email is required!"}) } plan := models.Plan{ @@ -91,12 +91,12 @@ func EditUser(c echo.Context) error { } planCount, err := db.NewSelect().Model(&plan).WherePK().Count(context.Background()) if err != nil || planCount == 0 { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Invalid plan id!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Invalid plan id!"}) } userId, err := uuid.Parse(id) if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "An unknown error occoured!"}) } var userData models.User @@ -104,7 +104,7 @@ func EditUser(c echo.Context) error { err = db.NewSelect().Model(&userData).WherePK().Relation("Plan").Scan(context.Background()) if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if userEditData.Username != "" { @@ -118,7 +118,7 @@ func EditUser(c echo.Context) error { if userEditData.Password != "" { hash, err := bcrypt.GenerateFromPassword([]byte(userEditData.Password), 12) if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } userData.PasswordHash = string(hash) @@ -134,48 +134,48 @@ func EditUser(c echo.Context) error { _, err = db.NewUpdate().Model(&userData).WherePK().Exec(context.Background()) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } - return c.JSON(http.StatusOK, map[string]string{"message": "Successfully updated user"}) + return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Successfully updated user"}) } -func GetPlans(c echo.Context) error { - db := c.Get("db").(*bun.DB) +func GetPlans(c fiber.Ctx) error { + db := db.GetDB() var plans []models.Plan err := db.NewSelect().Model(&plans).Scan(context.Background()) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } - return c.JSON(http.StatusOK, plans) + return c.Status(http.StatusOK).JSON(plans) } -func CreateUser(c echo.Context) error { - var signupData models.SignupData +func CreateUser(c fiber.Ctx) error { + signupData := new(models.SignupData) - if err := c.Bind(&signupData); err != nil { + if err := c.Bind().JSON(signupData); err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if signupData.Username == "" || signupData.Password == "" || signupData.Email == "" { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A password, username and email are required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A password, username and email are required!"}) } // if email is not valid if !regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`).MatchString(signupData.Email) { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A valid email is required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A valid email is required!"}) } - db := c.Get("db").(*bun.DB) + db := db.GetDB() hash, err := bcrypt.GenerateFromPassword([]byte(signupData.Password), 12) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } user := &models.User{ @@ -187,13 +187,13 @@ func CreateUser(c echo.Context) error { _, err = db.NewInsert().Model(user).Exec(context.Background()) if err != nil { - return c.JSON(http.StatusConflict, map[string]string{"message": "A user with that email or username already exists!"}) + return c.Status(http.StatusConflict).JSON(fiber.Map{"message": "A user with that email or username already exists!"}) } err = db.NewSelect().Model(user).WherePK().Relation("Plan").Scan(context.Background()) if err != nil { fmt.Println(err) - return c.JSON(http.StatusNotFound, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "An unknown error occoured!"}) } err = os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID), os.ModePerm) @@ -202,13 +202,13 @@ func CreateUser(c echo.Context) error { return err } - return c.JSON(http.StatusOK, map[string]string{"message": "Successfully created user"}) + return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Successfully created user"}) } // Stolen from Gitea https://github.com/go-gitea/gitea -func SystemStatus(c echo.Context) error { +func SystemStatus(c fiber.Ctx) error { updateSystemStatus() - return c.JSON(http.StatusOK, map[string]interface{}{ + return c.Status(http.StatusOK).JSON(fiber.Map{ "uptime": sysStatus.StartTime, "num_goroutine": sysStatus.NumGoroutine, "cur_mem_usage": sysStatus.MemAllocated, diff --git a/routes/auth.go b/routes/auth.go index 31e17d9..2df2a17 100644 --- a/routes/auth.go +++ b/routes/auth.go @@ -2,6 +2,7 @@ package routes import ( "context" + "filething/db" "filething/models" "fmt" "net/http" @@ -9,31 +10,34 @@ import ( "regexp" "time" + "github.com/gofiber/fiber/v3" "github.com/google/uuid" - "github.com/labstack/echo/v4" "github.com/uptrace/bun" "golang.org/x/crypto/bcrypt" ) -func LoginHandler(c echo.Context) error { - var loginData models.LoginData +func LoginHandler(c fiber.Ctx) error { + loginData := new(models.LoginData) - if err := c.Bind(&loginData); err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + if err := c.Bind().JSON(loginData); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "An unknown error occoured!"}) } + // Validate required fields if loginData.UsernameOrEmail == "" || loginData.Password == "" { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A password, and username or email are required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A Username/Email and Password are required"}) } - db := c.Get("db").(*bun.DB) + db := db.GetDB() user := new(models.User) - err := db.NewSelect().Model(user).Where("email = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background()) - if err != nil { - err := db.NewSelect().Model(user).Where("username = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background()) + var err error + + // Try both username and email for login attempts + if err = db.NewSelect().Model(user).Where("email = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background()); err != nil { + err = db.NewSelect().Model(user).Where("username = ?", loginData.UsernameOrEmail).Relation("Plan").Scan(context.Background()) if err != nil { - return c.JSON(http.StatusNotFound, map[string]string{"message": "User with that username or email not found!"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "User not found"}) } } @@ -46,49 +50,48 @@ func LoginHandler(c echo.Context) error { user.Usage = storageUsage session, err := GenerateSessionToken(db, user.ID) - if err != nil { - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "Internal server error"}) } expiration := time.Now().Add(time.Hour * 24 * 365 * 100) - c.SetCookie(&http.Cookie{ + c.Cookie(&fiber.Cookie{ Name: "sessionToken", Value: session.ID.String(), - SameSite: http.SameSiteStrictMode, + SameSite: "Strict", Expires: expiration, Path: "/", }) - return c.JSON(http.StatusOK, user) + // Return user data with status code 200 (OK) + return c.JSON(user) } var firstUserCreated *bool -func SignupHandler(c echo.Context) error { - var signupData models.SignupData +func SignupHandler(c fiber.Ctx) error { + signupData := new(models.SignupData) - if err := c.Bind(&signupData); err != nil { - fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + if err := c.Bind().JSON(signupData); err != nil { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if signupData.Username == "" || signupData.Password == "" || signupData.Email == "" { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A password, username and email are required!"}) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "A password, username and email are required!"}) } // if email is not valid if !regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`).MatchString(signupData.Email) { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A valid email is required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A valid email is required!"}) } - db := c.Get("db").(*bun.DB) + db := db.GetDB() hash, err := bcrypt.GenerateFromPassword([]byte(signupData.Password), 12) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if firstUserCreated == nil { @@ -111,7 +114,7 @@ func SignupHandler(c echo.Context) error { _, err = db.NewInsert().Model(user).Exec(context.Background()) if err != nil { - return c.JSON(http.StatusConflict, map[string]string{"message": "A user with that email or username already exists!"}) + return c.Status(http.StatusConflict).JSON(fiber.Map{"message": "A user with that email or username already exists!"}) } if !*firstUserCreated { @@ -121,7 +124,7 @@ func SignupHandler(c echo.Context) error { err = db.NewSelect().Model(user).WherePK().Relation("Plan").Scan(context.Background()) if err != nil { fmt.Println(err) - return c.JSON(http.StatusNotFound, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "An unknown error occoured!"}) } err = os.Mkdir(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID), os.ModePerm) @@ -134,20 +137,20 @@ func SignupHandler(c echo.Context) error { if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } expiration := time.Now().Add(time.Hour * 24 * 365 * 100) - c.SetCookie(&http.Cookie{ + c.Cookie(&fiber.Cookie{ Name: "sessionToken", Value: session.ID.String(), - SameSite: http.SameSiteStrictMode, + SameSite: "Strict", Expires: expiration, Path: "/", }) - return c.JSON(http.StatusOK, user) + return c.Status(http.StatusOK).JSON(user) } func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) { @@ -160,11 +163,11 @@ func GenerateSessionToken(db *bun.DB, userId uuid.UUID) (*models.Session, error) return session, err } -func GetUser(c echo.Context) error { - if c.Param("id") == "" { - user := c.Get("user") +func GetUser(c fiber.Ctx) error { + if c.Params("id") == "" { + user := c.Locals("user") if user == nil { - return c.JSON(http.StatusNotFound, map[string]string{"message": "User not found"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "User not found"}) } basePath := fmt.Sprintf("%s/%s/", os.Getenv("STORAGE_PATH"), user.(*models.User).ID) @@ -175,23 +178,23 @@ func GetUser(c echo.Context) error { user.(*models.User).Usage = storageUsage - return c.JSON(http.StatusOK, user.(*models.User)) + return c.Status(http.StatusOK).JSON(user.(*models.User)) } else { // get a user from the db using the id parameter, this *should* only be used for admin since /api/admin/users/:id has // a middleware that checks if the user is an admin, and it should be impossible to pass a param to this endpoint if it isnt that route - db := c.Get("db").(*bun.DB) + db := db.GetDB() user := new(models.User) - userId, err := uuid.Parse(c.Param("id")) + userId, err := uuid.Parse(c.Params("id")) if err != nil { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "An unknown error occoured!"}) } user.ID = userId err = db.NewSelect().Model(user).WherePK().Relation("Plan").Scan(context.Background()) if err != nil { - return c.JSON(http.StatusNotFound, map[string]string{"message": "User not found"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "User not found"}) } basePath := fmt.Sprintf("%s/%s/", os.Getenv("STORAGE_PATH"), user.ID) @@ -202,24 +205,21 @@ func GetUser(c echo.Context) error { user.Usage = storageUsage - return c.JSON(http.StatusOK, user) + return c.Status(http.StatusOK).JSON(user) } } -func LogoutHandler(c echo.Context) error { - db := c.Get("db").(*bun.DB) +func LogoutHandler(c fiber.Ctx) error { + db := db.GetDB() - cookie, err := c.Cookie("sessionToken") - if err != nil { - if err == http.ErrNoCookie { - return echo.NewHTTPError(http.StatusUnauthorized, "Session token missing") - } - return echo.NewHTTPError(http.StatusBadRequest, "Bad request") + cookie := c.Cookies("sessionToken") + if cookie == "" { + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"message": "Session token missing"}) } - sessionId, err := uuid.Parse(cookie.Value) + sessionId, err := uuid.Parse(cookie) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Bad request") + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "Bad request"}) } session := &models.Session{ @@ -229,8 +229,8 @@ func LogoutHandler(c echo.Context) error { if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } - return c.JSON(http.StatusOK, map[string]string{"message": "Succesfully logged out"}) + return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Succesfully logged out"}) } diff --git a/routes/files.go b/routes/files.go index 5a25bdc..590783d 100644 --- a/routes/files.go +++ b/routes/files.go @@ -12,7 +12,7 @@ import ( "path/filepath" "strings" - "github.com/labstack/echo/v4" + "github.com/gofiber/fiber/v3" ) type UploadResponse struct { @@ -20,10 +20,10 @@ type UploadResponse struct { File File `json:"file"` } -func UploadFile(c echo.Context) error { - user := c.Get("user").(*models.User) +func UploadFile(c fiber.Ctx) error { + user := c.Locals("user").(*models.User) - fullPath := strings.Trim(c.Param("*"), "/") + fullPath := strings.Trim(c.Params("*"), "/") basePath := fmt.Sprintf("%s/%s/%s/", os.Getenv("STORAGE_PATH"), user.ID, fullPath) currentUsage, err := calculateStorageUsage(fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID)) @@ -44,12 +44,12 @@ func UploadFile(c echo.Context) error { } } - reader, err := c.Request().MultipartReader() + form, err := c.MultipartForm() if err != nil { if err == http.ErrNotMultipart { if directoryExists { // Directories exist, but no file was uploaded - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A folder with that name already exists"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A folder with that name already exists"}) } // Directories were just created, and no file was provided entry, err := os.Stat(basePath) @@ -69,22 +69,18 @@ func UploadFile(c echo.Context) error { }, } - return c.JSON(http.StatusOK, uploadFile) + return c.Status(http.StatusOK).JSON(uploadFile) } fmt.Println(err) return err } - part, err := reader.NextPart() - if err != nil { - fmt.Println(err) - return err - } + file := form.File["file"][0] - filepath := filepath.Join(basePath, part.FileName()) + filepath := filepath.Join(basePath, file.Filename) if _, err = os.Stat(filepath); err == nil { - return c.JSON(http.StatusConflict, map[string]string{"message": "File with that name already exists"}) + return c.Status(http.StatusConflict).JSON(fiber.Map{"message": "File with that name already exists"}) } dst, err := os.Create(filepath) @@ -95,16 +91,22 @@ func UploadFile(c echo.Context) error { defer dst.Close() // Read the file manually because otherwise we are limited by the arbitrarily small size of /tmp - buffer := make([]byte, 4096) + buffer := make([]byte, 1*1024*1024) totalSize := int64(0) + fd, err := file.Open() + if err != nil { + fmt.Println(err) + return err + } + for { - n, readErr := part.Read(buffer) + n, readErr := fd.Read(buffer) if readErr != nil && readErr == io.ErrUnexpectedEOF { dst.Close() os.Remove(filepath) - return c.JSON(http.StatusRequestTimeout, map[string]string{"message": "Upload canceled"}) + return c.Status(http.StatusRequestTimeout).JSON(fiber.Map{"message": "Upload canceled"}) } if readErr != nil && readErr != io.EOF { @@ -117,7 +119,7 @@ func UploadFile(c echo.Context) error { if currentUsage+totalSize > user.Plan.MaxStorage { dst.Close() os.Remove(filepath) - return c.JSON(http.StatusInsufficientStorage, map[string]string{"message": "Insufficient storage space"}) + return c.Status(http.StatusInsufficientStorage).JSON(fiber.Map{"message": "Insufficient storage space"}) } if _, err := dst.Write(buffer[:n]); err != nil { @@ -143,7 +145,7 @@ func UploadFile(c echo.Context) error { }, } - return c.JSON(http.StatusOK, uploadFile) + return c.Status(http.StatusOK).JSON(uploadFile) } } } @@ -185,10 +187,10 @@ type File struct { LastModified string `json:"last_modified"` } -func GetFiles(c echo.Context) error { - user := c.Get("user").(*models.User) +func GetFiles(c fiber.Ctx) error { + user := c.Locals("user").(*models.User) - fullPath := strings.Trim(c.Param("*"), "/") + fullPath := strings.Trim(c.Params("*"), "/") basePath := fmt.Sprintf("%s/%s/%s/", os.Getenv("STORAGE_PATH"), user.ID, fullPath) f, err := os.Open(basePath) @@ -214,22 +216,22 @@ func GetFiles(c echo.Context) error { }) } - return c.JSON(http.StatusOK, jsonFiles) + return c.Status(http.StatusOK).JSON(jsonFiles) } -func GetFile(c echo.Context) error { - user := c.Get("user").(*models.User) +func GetFile(c fiber.Ctx) error { + user := c.Locals("user").(*models.User) - fullPath := strings.Trim(c.Param("*"), "/") + fullPath := strings.Trim(c.Params("*"), "/") - fileNamesParam := c.QueryParam("filenames") + fileNamesParam := c.Query("filenames") var fileNames []string if fileNamesParam != "" { fileNames = strings.Split(fileNamesParam, ",") } if fullPath == "" && len(fileNames) == 0 { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "A file is required"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "A file is required"}) } basePath := fmt.Sprintf("%s/%s", os.Getenv("STORAGE_PATH"), user.ID) @@ -239,12 +241,12 @@ func GetFile(c echo.Context) error { fileInfo, err := os.Stat(basePath) if err != nil { - return c.JSON(http.StatusNotFound, map[string]string{"message": "No file found!"}) + return c.Status(http.StatusNotFound).JSON(fiber.Map{"message": "No file found!"}) } var buf bytes.Buffer if fileInfo.IsDir() { - c.Response().Header().Set(echo.HeaderContentType, "application/zip") + c.Type("application/zip") if len(fileNames) != 0 { err := zipFiles(&buf, filepath.Join(basePath, fullPath), fileNames) @@ -253,7 +255,7 @@ func GetFile(c echo.Context) error { return err } - _, err = buf.WriteTo(c.Response().Writer) + _, err = buf.WriteTo(c.Response().BodyWriter()) return err } @@ -263,10 +265,10 @@ func GetFile(c echo.Context) error { return err } - _, err = buf.WriteTo(c.Response().Writer) + _, err = buf.WriteTo(c.Response().BodyWriter()) return err } else { - return c.File(basePath) + return c.SendFile(basePath) } } @@ -343,21 +345,21 @@ type DeleteRequest struct { Files []File `json:"files"` } -func DeleteFiles(c echo.Context) error { - var deleteData DeleteRequest +func DeleteFiles(c fiber.Ctx) error { + deleteData := new(DeleteRequest) - if err := c.Bind(&deleteData); err != nil { + if err := c.Bind().JSON(deleteData); err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } if len(deleteData.Files) == 0 { - return c.JSON(http.StatusBadRequest, map[string]string{"message": "Files are required!"}) + return c.Status(http.StatusBadRequest).JSON(fiber.Map{"message": "Files are required!"}) } - user := c.Get("user").(*models.User) + user := c.Locals("user").(*models.User) - fullPath := strings.Trim(c.Param("*"), "/") + fullPath := strings.Trim(c.Params("*"), "/") basePath := fmt.Sprintf("%s/%s/%s", os.Getenv("STORAGE_PATH"), user.ID, fullPath) for _, file := range deleteData.Files { @@ -365,7 +367,7 @@ func DeleteFiles(c echo.Context) error { err := os.RemoveAll(path) if err != nil { fmt.Println(err) - return c.JSON(http.StatusInternalServerError, map[string]string{"message": "An unknown error occoured!"}) + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"message": "An unknown error occoured!"}) } } @@ -376,5 +378,5 @@ func DeleteFiles(c echo.Context) error { word = word + "s" } - return c.JSON(http.StatusOK, map[string]string{"message": fmt.Sprintf("Successfully deleted %d %s", fileLen, word)}) + return c.Status(http.StatusOK).JSON(fiber.Map{"message": fmt.Sprintf("Successfully deleted %d %s", fileLen, word)}) } diff --git a/server.go b/server.go index cc72336..5e0f2a1 100644 --- a/server.go +++ b/server.go @@ -5,12 +5,13 @@ package main import ( "filething/ui" + "fmt" "io/fs" - "net/http" "path/filepath" "strings" - "github.com/labstack/echo/v4" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/static" ) type embeddedFS struct { @@ -21,55 +22,61 @@ type embeddedFS struct { func (fs *embeddedFS) Open(name string) (fs.File, error) { // Prepend the prefix to the requested file name publicPath := filepath.Join(fs.prefix, name) - return fs.baseFS.Open(publicPath) + fmt.Println("Reading file:", publicPath) + file, err := fs.baseFS.Open(publicPath) + if err != nil { + return nil, fmt.Errorf("file not found: %s", publicPath) + } + + fmt.Println("File found:", publicPath, file) + + return file, err } var publicFS = &embeddedFS{ - baseFS: ui.DistDirFS, + baseFS: ui.DistDir, prefix: "public/", } func init() { - initUi = func(e *echo.Echo) { - e.GET("/*", echo.StaticDirectoryHandler(publicFS, false)) + initUi = func(app *fiber.App) { + app.Get("/*", static.New("", static.Config{ + FS: publicFS, + })) - e.HTTPErrorHandler = customHTTPErrorHandler - } -} - -// Custom Error handling since Nuxt relies on the 404 page for dynamic pages we still want api routes to use the default -// error handling built into echo -func customHTTPErrorHandler(err error, c echo.Context) { - if he, ok := err.(*echo.HTTPError); ok && he.Code == http.StatusNotFound { - path := c.Request().URL.Path - - if !strings.HasPrefix(path, "/api") { - file, err := publicFS.Open("404.html") - if err != nil { - c.Logger().Error(err) + app.Use(func(c fiber.Ctx) error { + err := c.Next() + if err == nil { + return nil } - fileInfo, err := file.Stat() - if err != nil { - c.Logger().Error(err) - } + if fiber.ErrNotFound == err { + path := c.Path() + if !strings.HasPrefix(path, "/api") { + file, err := publicFS.Open("404.html") + if err != nil { + c.App().Server().Logger.Printf("Error opening 404.html: %s", err) + return err + } + defer file.Close() - fileBuf := make([]byte, fileInfo.Size()) - _, err = file.Read(fileBuf) - defer func() { - if err := file.Close(); err != nil { - panic(err) + fileInfo, err := file.Stat() + if err != nil { + c.App().Server().Logger.Printf("An error occurred while getting the file info: %s", err) + return err + } + + fileBuf := make([]byte, fileInfo.Size()) + _, err = file.Read(fileBuf) + if err != nil { + c.App().Server().Logger.Printf("An error occurred while reading the file: %s", err) + return err + } + + return c.Status(fiber.StatusNotFound).SendString(string(fileBuf)) } - }() - if err != nil { - c.Logger().Error(err) - panic(err) } - - c.HTML(http.StatusNotFound, string(fileBuf)) - return - } + return err + }) } - - c.Echo().DefaultHTTPErrorHandler(err, c) } diff --git a/server_dev.go b/server_dev.go index 5e9cdb0..4d5604e 100644 --- a/server_dev.go +++ b/server_dev.go @@ -4,24 +4,53 @@ package main import ( - "net/url" + "strings" - "github.com/labstack/echo/v4" - echoMiddleware "github.com/labstack/echo/v4/middleware" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/proxy" + fastProxy "github.com/yeqown/fasthttp-reverse-proxy/v2" ) func init() { - initUi = func(e *echo.Echo) { - spawnProcess("bun", []string{"--cwd=ui", "run", "dev"}, e) + initUi = func(app *fiber.App) { + if !fiber.IsChild() { + spawnProcess("bun", []string{"--cwd=ui", "run", "dev"}, app) + } target := "localhost:3000" - e.Group("/*").Use(echoMiddleware.ProxyWithConfig(echoMiddleware.ProxyConfig{ - Balancer: echoMiddleware.NewRoundRobinBalancer([]*echoMiddleware.ProxyTarget{ - {URL: &url.URL{ - Scheme: "http", - Host: target, - }}, - }), - })) + app.All("/*", func(c fiber.Ctx) error { + path := c.Path() + if strings.HasPrefix(path, "/api") { + return c.Next() + } + + request := c.Request().URI() + if string(request.RequestURI()) == "/_nuxt/" { + return proxyWebSocket(c, target) + } + + return proxy.Do(c, "http://"+target+string(request.RequestURI())) + }) } } + +var proxyServer *fastProxy.ReverseProxy + +func proxyWebSocket(c fiber.Ctx, target string) error { + path := c.Path() + // proxyServer, err := fastProxy.NewWSReverseProxyWith( + // fastProxy.WithURL_OptionWS("ws://localhost:3000"+path), + // fastProxy.WithDynamicPath_OptionWS(true, fastProxy.DefaultOverrideHeader), + // ) + if proxyServer == nil { + proxyServer, err = fastProxy.NewWSReverseProxyWith( + fastProxy.WithURL_OptionWS("ws://localhost:3000"+path), + fastProxy.WithDynamicPath_OptionWS(true, fastProxy.DefaultOverrideHeader), + ) + if err != nil { + panic(err) + } + } + proxyServer.ServeHTTP(c.Context()) + return nil +} diff --git a/server_ssr.go b/server_ssr.go index c36c458..2f6cf0c 100644 --- a/server_ssr.go +++ b/server_ssr.go @@ -7,38 +7,70 @@ import ( "embed" "filething/ui" "io" - "net/url" "os" "path/filepath" + "strings" - "github.com/labstack/echo/v4" - echoMiddleware "github.com/labstack/echo/v4/middleware" + "github.com/gofiber/fiber/v3" + "github.com/gofiber/fiber/v3/middleware/proxy" ) +// func init() { +// initUi = func(app *fiber.App) { +// tmpDir, err := os.MkdirTemp("", "filething-ssr") +// if err != nil { +// panic(err) +// } + +// err = copyEmbeddedFiles(ui.DistDir, ".output", tmpDir) +// if err != nil { +// panic(err) +// } + +// path := filepath.Join(tmpDir, "server/index.mjs") +// spawnProcess("node", []string{path}, app) + +// // target := "localhost:3000" +// // e.Group("/*").Use(echoMiddleware.ProxyWithConfig(echoMiddleware.ProxyConfig{ +// // Balancer: echoMiddleware.NewRoundRobinBalancer([]*echoMiddleware.ProxyTarget{ +// // {URL: &url.URL{ +// // Scheme: "http", +// // Host: target, +// // }}, +// // }), +// // })) +// app.Get("/*", proxy.Forward("http://localhost:3000")) +// } +// } + func init() { - initUi = func(e *echo.Echo) { - tmpDir, err := os.MkdirTemp("", "filething-ssr") - if err != nil { - panic(err) - } + initUi = func(app *fiber.App) { + if !fiber.IsChild() { + tmpDir, err := os.MkdirTemp("", "filething-ssr") + if err != nil { + panic(err) + } - err = copyEmbeddedFiles(ui.DistDir, ".output", tmpDir) - if err != nil { - panic(err) - } + err = copyEmbeddedFiles(ui.DistDir, ".output", tmpDir) + if err != nil { + panic(err) + } - path := filepath.Join(tmpDir, "server/index.mjs") - spawnProcess("node", []string{path}, e) + path := filepath.Join(tmpDir, "server/index.mjs") + + spawnProcess("node", []string{path}, app) + } target := "localhost:3000" - e.Group("/*").Use(echoMiddleware.ProxyWithConfig(echoMiddleware.ProxyConfig{ - Balancer: echoMiddleware.NewRoundRobinBalancer([]*echoMiddleware.ProxyTarget{ - {URL: &url.URL{ - Scheme: "http", - Host: target, - }}, - }), - })) + app.All("/*", func(c fiber.Ctx) error { + path := c.Path() + if strings.HasPrefix(path, "/api") { + return c.Next() + } + + request := c.Request().URI() + return proxy.Do(c, "http://"+target+string(request.RequestURI())) + }) } } diff --git a/ui/composables/useUser.ts b/ui/composables/useUser.ts index 1b11854..b069431 100644 --- a/ui/composables/useUser.ts +++ b/ui/composables/useUser.ts @@ -20,7 +20,7 @@ export const useUser = () => { // Fetch the user only if it's uninitialized (i.e., null) const getUser = async () => { - if (!user.value.fetched) { + if (!user.value.fetched && useCookie('sessionToken').value) { await fetchUser() } diff --git a/ui/embed.go b/ui/embed.go index d7f4601..7e7d0fe 100644 --- a/ui/embed.go +++ b/ui/embed.go @@ -3,12 +3,7 @@ package ui import ( "embed" - - "github.com/labstack/echo/v4" ) //go:embed all:.output var DistDir embed.FS - -// DistDirFS contains the embedded dist directory files (without the "dist" prefix) -var DistDirFS = echo.MustSubFS(DistDir, ".output/") diff --git a/ui/pages/login.vue b/ui/pages/login.vue index 424992a..8e0fb0e 100644 --- a/ui/pages/login.vue +++ b/ui/pages/login.vue @@ -42,8 +42,8 @@ onUnmounted(() => {
{{ error }}
diff --git a/utils.go b/utils.go index 23f93d4..b299cc6 100644 --- a/utils.go +++ b/utils.go @@ -1,17 +1,16 @@ package main import ( - "context" "fmt" "os" "os/exec" "os/signal" "syscall" - "github.com/labstack/echo/v4" + "github.com/gofiber/fiber/v3" ) -func spawnProcess(cmd string, args []string, e *echo.Echo) error { +func spawnProcess(cmd string, args []string, app *fiber.App) error { shutdown := make(chan os.Signal, 1) signal.Notify(shutdown, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func() { @@ -45,7 +44,7 @@ func spawnProcess(cmd string, args []string, e *echo.Echo) error { fmt.Println("sub process server stopped") - if err := e.Shutdown(context.Background()); err != nil { + if err := app.Shutdown(); err != nil { fmt.Println("Error shutting down HTTP server:", err) } }()