diff --git a/.env.example b/.env.example index e7a0bf9..190a694 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1 @@ -# Optional - with default values - -PORT=3000 -AUTO_MIGRATE=true - -# Mandatory - -DATABASE_URL=db.sqlite -JWT_SECRET=secrettt +ENV=prod \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ce8578..582042f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ go.work # Database db.sqlite3 db.sqlite +pb_data/ # Air tmp diff --git a/Dockerfile b/Dockerfile index 26f6593..80492ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the Go binary -FROM golang:1.23-alpine AS goapp +FROM golang:1.24-alpine AS goapp WORKDIR /app @@ -20,4 +20,4 @@ FROM alpine:latest as release COPY --from=goapp /app/goapp /goapp WORKDIR /app -CMD ["/goapp", "serve"] +CMD ["/goapp", "serve", "--http", ":8090"] diff --git a/Makefile b/Makefile index 16b7606..714f521 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ all: statics templ statics: - curl https://cdn.tailwindcss.com/3.4.16 --output internal/embeded/statics/tailwind.js - curl https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js --output internal/embeded/statics/htmx.js + curl https://cdn.tailwindcss.com/3.4.16 --output internal/embeded/assets/tailwind.js + curl https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js --output internal/embeded/assets/htmx.js templ: templ generate diff --git a/README.md b/README.md index bc96221..7c98d88 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # goth oidc ## Components -- Go + Fiber +- Go + Pocketbase - Tailwind - HTMX -- SQLite -- Oidc auth ## Extenal dependencies @@ -30,20 +28,13 @@ Before running be sure to add all required environment variables (see [env examp - make: `make statics` to download statics - serve: `go run main.go serve` or `air serve` -- migrate up: `go run main.go migrate:up` or `air migrate:up` -- migrate down: `go run main.go migrate:down N` or `air migrate:down N` where N is the number of migrations down +- migrate collections: `go run main.go migrate collections` or `air migrate collections` ## Roadmap ### Done - Go - HTMx -- Tailwind - Templ -- Custom auth -- JWT cookie auth -- Embeded Migrations - -### Next -- [ ] Access + refresh tokens -- [ ] SQL Autogeneration with [sqlc](https://github.com/sqlc-dev/sqlc) +- Tailwind +- Pocketbase diff --git a/go.mod b/go.mod index baa7536..b47c80f 100644 --- a/go.mod +++ b/go.mod @@ -1,53 +1,49 @@ module goth -go 1.23.0 +go 1.24 toolchain go1.24.1 require ( github.com/a-h/templ v0.3.857 - github.com/gofiber/fiber/v2 v2.49.0 - github.com/golang-jwt/jwt/v5 v5.0.0 - github.com/golang-migrate/migrate/v4 v4.16.2 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 - github.com/spf13/cobra v1.7.0 - golang.org/x/crypto v0.32.0 - modernc.org/sqlite v1.26.0 + github.com/pocketbase/dbx v1.11.0 + github.com/pocketbase/pocketbase v0.27.1 + modernc.org/sqlite v1.37.0 ) require ( - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/disintegration/imaging v1.6.2 // indirect + github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/ganigeorgiev/fexpr v0.5.0 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect + github.com/go-sql-driver/mysql v1.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.17.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.48.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/tools v0.24.0 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.24.1 // indirect - modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.6.0 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.0.1 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/testify v1.8.1 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/image v0.26.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/oauth2 v0.29.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + modernc.org/libc v1.62.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.9.1 // indirect ) diff --git a/go.sum b/go.sum index 223bff2..cfb782d 100644 --- a/go.sum +++ b/go.sum @@ -1,110 +1,136 @@ github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg= github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8= +github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= 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/gofiber/fiber/v2 v2.49.0 h1:xBVG2c66GDcWfww56xHvMn52Q0XX7UrSvjj6MD8/5EE= -github.com/gofiber/fiber/v2 v2.49.0/go.mod h1:oxpt7wQaEYgdDmq7nMxCGhilYicBLFnZ+jQSJcQDlSE= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= -github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk= +github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 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/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU= +github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= +github.com/pocketbase/pocketbase v0.27.1 h1:KGCsS8idUVTC5QHxTj91qHDhIXOb5Yb50wwHhNvJRTQ= +github.com/pocketbase/pocketbase v0.27.1/go.mod h1:aTpwwloVJzeJ7MlwTRrbI/x62QNR2/kkCrovmyrXpqs= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -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.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc= -github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= -modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= -modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= -modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= -modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= -modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= +modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= +modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= +modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= +modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= +modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/cmd/migrate/migrate.go b/internal/cmd/migrate/migrate.go deleted file mode 100644 index 5f9ddc4..0000000 --- a/internal/cmd/migrate/migrate.go +++ /dev/null @@ -1,56 +0,0 @@ -package migrate - -import ( - "fmt" - "goth/internal/config" - "goth/internal/embeded" - "strconv" - - "github.com/golang-migrate/migrate/v4" - _ "github.com/golang-migrate/migrate/v4/database/sqlite" - _ "github.com/golang-migrate/migrate/v4/source/file" - "github.com/golang-migrate/migrate/v4/source/iofs" - "github.com/spf13/cobra" -) - -type withMigrateFunc func(m *migrate.Migrate) - -func apply(fMigrate withMigrateFunc) { - cfg := config.GetMigrateConfigs() - - d, err := iofs.New(embeded.Migrations, "migrations") - checkErr("Failed to get embeded migrations", err) - - databaseURL := fmt.Sprintf("sqlite://%s", cfg.DataBaseURL) - - m, err := migrate.NewWithSourceInstance("iofs", d, databaseURL) - checkErr("Failed to get migrate", err) - defer m.Close() - - fMigrate(m) -} - -func Up(cmd *cobra.Command, args []string) { - apply(func(m *migrate.Migrate) { - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - checkErr("Failed to migrate up", err) - } - }) -} - -func Down(cmd *cobra.Command, args []string) { - n, err := strconv.Atoi(args[0]) - checkErr("Failed to parse argument", err) - - apply(func(m *migrate.Migrate) { - if err := m.Steps(-1 * n); err != nil && err != migrate.ErrNoChange { - checkErr("Failed to migrate down", err) - } - }) -} - -func checkErr(msg string, err error) { - if err != nil { - panic(fmt.Sprintf("[Migrate] %s: %s", msg, err.Error())) - } -} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 00c4591..f7d05f8 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -1,45 +1,27 @@ package cmd import ( - "goth/internal/cmd/migrate" - "goth/internal/cmd/server" + "goth/internal/config" + "goth/internal/routes" + "log" - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "goth", - Short: "goth app CLI", -} - -var serveCmd = &cobra.Command{ - Use: "serve", - Short: "Serve the application", - Run: server.Serve, -} + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/plugins/migratecmd" -var migrateUpCmd = &cobra.Command{ - Use: "migrate:up", - Short: "Migrates Up the database", - Run: migrate.Up, -} + _ "goth/internal/migrations" // register all migrations +) -var migrateDownCmd = &cobra.Command{ - Use: "migrate:down", - Short: "Migrates Down the database", - Run: migrate.Down, - Args: cobra.ExactArgs(1), -} +func Root() { + app := pocketbase.New() + cfg := config.GetServerConfigs() + app.OnServe().BindFunc(routes.SetupRoutes) + app.Settings().Meta.HideControls = cfg.IsProd -func init() { - rootCmd.AddCommand(serveCmd) - rootCmd.AddCommand(migrateUpCmd) - rootCmd.AddCommand(migrateDownCmd) -} + if !cfg.IsProd { + migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{}) + } -func Execute() { - err := rootCmd.Execute() - if err != nil { - panic(err.Error()) + if err := app.Start(); err != nil { + log.Fatal(err) } } diff --git a/internal/cmd/server/server.go b/internal/cmd/server/server.go deleted file mode 100644 index 36f7d17..0000000 --- a/internal/cmd/server/server.go +++ /dev/null @@ -1,33 +0,0 @@ -package server - -import ( - "goth/internal/cmd/migrate" - "goth/internal/config" - "goth/internal/routes" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/spf13/cobra" -) - -func Serve(cmd *cobra.Command, args []string) { - cfg := config.GetServerConfigs() - - if cfg.AutoMigrate { - migrate.Up(cmd, args) - } - - app := fiber.New(fiber.Config{ - ErrorHandler: routes.ErrorHandler, - }) - - app.Use(cors.New()) - - if err := routes.Init(app, cfg); err != nil { - panic(err.Error()) - } - - if err := app.Listen(cfg.ServerAddress); err != nil { - panic(err.Error()) - } -} diff --git a/internal/components/design.templ b/internal/components/design.templ index c4bf0b0..a1204ab 100644 --- a/internal/components/design.templ +++ b/internal/components/design.templ @@ -14,9 +14,9 @@ templ rawPage(title string) { { title } | GOTH - - - + + + { children... } @@ -55,13 +55,13 @@ templ ErrorPage() { } } -templ authenticatedPage(title string, breadcrumb, description string) { +templ headedPage(title string, breadcrumb, description string) { @rawPage(title) {
if description != "" {

{description}

@@ -74,79 +74,10 @@ templ authenticatedPage(title string, breadcrumb, description string) { } } -templ PostLogoutPage() { - @rawPage("Logout") { -
-
-
- Home -
- Login - Register -
-
-

You need to login to proceed

-
-
- } -} - -templ LoginPage() { - @rawPage("Login") { -
-
-
- Login -
- Register -
-
-
-
- - -
-
- - -
- -
-
-
- } -} - -templ RegisterPage() { - @rawPage("Register") { -
-
-
- Register -
- Login -
-
-
-
- - -
-
- - -
- -
-
-
- } -} - // Tasks Views templ TaskListPage(tasks []models.Task) { - @authenticatedPage("Tasks List", "Tasks", "Open Tasks To Do") { + @headedPage("Tasks List", "Tasks", "Open Tasks To Do") {
    if len(tasks) > 0 { @@ -169,7 +100,7 @@ templ TaskListPage(tasks []models.Task) { } templ TaskEditPage(task models.Task) { - @authenticatedPage("Task Editor", fmt.Sprintf("Tasks > %s", task.Title), "Edit this task") { + @headedPage("Task Editor", fmt.Sprintf("Tasks > %s", task.Title), "Edit this task") {
    diff --git a/internal/config/config.go b/internal/config/config.go index 0113ef7..e380626 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,59 +7,15 @@ import ( ) type ServerConfigs struct { - AutoMigrate bool - ServerAddress string - - DataBaseURL string - JwtSecret string + IsProd bool } func GetServerConfigs() ServerConfigs { config := ServerConfigs{} - // mandatory - - config.DataBaseURL = os.Getenv("DATABASE_URL") - if config.DataBaseURL == "" { - panic("Missing DATABASE_URL") - } - - config.JwtSecret = os.Getenv("JWT_SECRET") - if config.JwtSecret == "" { - panic("Missing JWT_SECRET") - } - - // optional - with defaults - - envAutoMigrate := os.Getenv("AUTO_MIGRATE") - if envAutoMigrate != "" { - config.AutoMigrate = envAutoMigrate == "true" - } else { - config.AutoMigrate = true - } - - envPort := os.Getenv("PORT") - if envPort != "" { - config.ServerAddress = ":" + envPort - } else { - config.ServerAddress = ":3000" - } - - return config -} - -type MigrateConfigs struct { - DataBaseURL string -} - -func GetMigrateConfigs() MigrateConfigs { - config := MigrateConfigs{} - - // mandatory - - config.DataBaseURL = os.Getenv("DATABASE_URL") - if config.DataBaseURL == "" { - panic("Missing DATABASE_URL") + config.IsProd = true + if os.Getenv("ENV") == "local" { + config.IsProd = false } return config diff --git a/internal/controllers/controllers.go b/internal/controllers/controllers.go deleted file mode 100644 index 152828d..0000000 --- a/internal/controllers/controllers.go +++ /dev/null @@ -1,5 +0,0 @@ -package controllers - -type Controllers interface { - UserController | TaskController -} diff --git a/internal/controllers/tasks.go b/internal/controllers/tasks.go deleted file mode 100644 index edfebc9..0000000 --- a/internal/controllers/tasks.go +++ /dev/null @@ -1,81 +0,0 @@ -package controllers - -import ( - "database/sql" - "goth/internal/models" - "goth/internal/repositories/database" - - "github.com/gofiber/fiber/v2" -) - -type TaskController struct { - DbRepo database.Database -} - -func NewTaskController(dbRepo database.Database) *TaskController { - return &TaskController{ - DbRepo: dbRepo, - } -} - -func (tc *TaskController) CreateTask(ownerId string) (models.Task, error) { - task := models.Task{ - Id: models.GenerateId(), - Title: "New Task", - Description: "New Task Description", - OwnerId: ownerId, - } - return task, tc.DbRepo.CreateTask(task) -} - -func (tc *TaskController) ListTasks(ownerId string) ([]models.Task, error) { - return tc.DbRepo.ListTasksByOwner(ownerId) -} - -func (tc *TaskController) RetrieveTask(ownerId, taskId string) (models.Task, error) { - task, err := tc.DbRepo.RetrieveTaskById(taskId) - if err != nil { - if err == sql.ErrNoRows { - return models.EmptyTask, fiber.ErrNotFound - } - - return models.EmptyTask, err - } - - if task.OwnerId != ownerId { - return models.EmptyTask, fiber.ErrNotFound - } - - return task, nil -} - -func (tc *TaskController) DeleteTask(ownerId, taskId string) error { - _, err := tc.RetrieveTask(ownerId, taskId) - if err != nil { - return err - } - - return tc.DbRepo.DeleteTask(taskId) -} - -type TaskChange struct { - Title string `json:"title"` - Description string `json:"description"` -} - -func (tc *TaskController) UpdateTask(ownerId, taskId string, changes TaskChange) error { - task, err := tc.RetrieveTask(ownerId, taskId) - if err != nil { - return err - } - - if changes.Title != "" { - task.Title = changes.Title - } - - if changes.Description != "" { - task.Description = changes.Description - } - - return tc.DbRepo.UpdateTask(task) -} diff --git a/internal/controllers/users.go b/internal/controllers/users.go deleted file mode 100644 index 22a12e0..0000000 --- a/internal/controllers/users.go +++ /dev/null @@ -1,89 +0,0 @@ -package controllers - -import ( - "errors" - "goth/internal/models" - "goth/internal/repositories/database" - "goth/internal/repositories/jwt" - "goth/internal/utils" - "time" - - "github.com/gofiber/fiber/v2" -) - -type UserController struct { - dbRepo database.Database - jwtRepo jwt.JWT -} - -func NewUserController(dbRepo database.Database, jwtRepo jwt.JWT) *UserController { - return &UserController{dbRepo, jwtRepo} -} - -func (uc *UserController) VerifyJWTCookie(token string) (models.User, error) { - id, err := uc.jwtRepo.ParseJWT(token) - if err != nil { - return models.EmptyUser, err - } - - return uc.dbRepo.RetrieveUserById(id) -} - -type UserRequest struct { - Username string `json:"username"` - Password string `json:"password"` -} - -func (uc *UserController) Login(req UserRequest, cookieName string) (*fiber.Cookie, error) { - user, err := uc.dbRepo.RetrieveUserByName(req.Username) - if err != nil { - return nil, err - } - - // slow checking -> design feature of bcrypt - if !utils.CheckPasswordHash(req.Password, user.PswdHash) { - return nil, errors.New("bad username or password") - } - - expiration := time.Now().Add(7 * 24 * time.Hour) - jwt, err := uc.jwtRepo.GenerateJWT(user.ID, expiration) - if err != nil { - return nil, err - } - - return &fiber.Cookie{ - Expires: expiration, - Name: cookieName, - Value: jwt, - }, nil -} - -func (uc *UserController) Register(req UserRequest, cookieName string) (*fiber.Cookie, error) { - hashedPassword, err := utils.HashPassword(req.Password) - if err != nil { - return nil, err - } - - user := models.User{ - ID: models.GenerateId(), - Username: req.Username, - PswdHash: hashedPassword, - } - - err = uc.dbRepo.InsertUser(user) - if err != nil { - return nil, err - } - - expiration := time.Now().Add(7 * 24 * time.Hour) - jwt, err := uc.jwtRepo.GenerateJWT(user.ID, expiration) - if err != nil { - return nil, err - } - - return &fiber.Cookie{ - Expires: expiration, - Name: cookieName, - Value: jwt, - }, nil -} diff --git a/internal/embeded/.gitignore b/internal/embeded/.gitignore new file mode 100644 index 0000000..3ed09ab --- /dev/null +++ b/internal/embeded/.gitignore @@ -0,0 +1 @@ +assets/ \ No newline at end of file diff --git a/internal/embeded/statics.go b/internal/embeded/assets.go similarity index 57% rename from internal/embeded/statics.go rename to internal/embeded/assets.go index 7b05f85..0e9075b 100644 --- a/internal/embeded/statics.go +++ b/internal/embeded/assets.go @@ -4,5 +4,5 @@ import "embed" // Embed a directory // -//go:embed statics/* -var Statics embed.FS +//go:embed assets/* +var Assets embed.FS diff --git a/internal/embeded/migrations.go b/internal/embeded/migrations.go deleted file mode 100644 index 335ea71..0000000 --- a/internal/embeded/migrations.go +++ /dev/null @@ -1,8 +0,0 @@ -package embeded - -import "embed" - -// Embed a directory -// -//go:embed migrations/*.sql -var Migrations embed.FS diff --git a/internal/embeded/migrations/01_initial.down.sql b/internal/embeded/migrations/01_initial.down.sql deleted file mode 100644 index cc1f647..0000000 --- a/internal/embeded/migrations/01_initial.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE users; diff --git a/internal/embeded/migrations/01_initial.up.sql b/internal/embeded/migrations/01_initial.up.sql deleted file mode 100644 index 7ce5571..0000000 --- a/internal/embeded/migrations/01_initial.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- SQLITE - -CREATE TABLE users ( - id UUID PRIMARY KEY, - pswd_hash TEXT NOT NULL, - username TEXT NOT NULL UNIQUE, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file diff --git a/internal/embeded/migrations/02_tasks.down.sql b/internal/embeded/migrations/02_tasks.down.sql deleted file mode 100644 index 87f1ed5..0000000 --- a/internal/embeded/migrations/02_tasks.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE tasks; diff --git a/internal/embeded/migrations/02_tasks.up.sql b/internal/embeded/migrations/02_tasks.up.sql deleted file mode 100644 index dd98c06..0000000 --- a/internal/embeded/migrations/02_tasks.up.sql +++ /dev/null @@ -1,10 +0,0 @@ --- SQLITE - -CREATE TABLE tasks ( - id UUID PRIMARY KEY, - title TEXT NOT NULL, - owner_id UUID NOT NULL, - description TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (owner_id) REFERENCES users (id) -); diff --git a/internal/embeded/statics/favicon.ico b/internal/embeded/statics/favicon.ico deleted file mode 100644 index 2f23f50..0000000 Binary files a/internal/embeded/statics/favicon.ico and /dev/null differ diff --git a/internal/migrations/1745782908_collections_snapshot.go b/internal/migrations/1745782908_collections_snapshot.go new file mode 100644 index 0000000..c5d429d --- /dev/null +++ b/internal/migrations/1745782908_collections_snapshot.go @@ -0,0 +1,864 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + jsonData := `[ + { + "createRule": null, + "deleteRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text455797646", + "max": 0, + "min": 0, + "name": "collectionRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text127846527", + "max": 0, + "min": 0, + "name": "recordRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1582905952", + "max": 0, + "min": 0, + "name": "method", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": true, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": true, + "type": "autodate" + } + ], + "id": "pbc_2279338944", + "indexes": [ + "CREATE INDEX ` + "`" + `idx_mfas_collectionRef_recordRef` + "`" + ` ON ` + "`" + `_mfas` + "`" + ` (collectionRef,recordRef)" + ], + "listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "name": "_mfas", + "system": true, + "type": "base", + "updateRule": null, + "viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" + }, + { + "createRule": null, + "deleteRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text455797646", + "max": 0, + "min": 0, + "name": "collectionRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text127846527", + "max": 0, + "min": 0, + "name": "recordRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "cost": 8, + "hidden": true, + "id": "password901924565", + "max": 0, + "min": 0, + "name": "password", + "pattern": "", + "presentable": false, + "required": true, + "system": true, + "type": "password" + }, + { + "autogeneratePattern": "", + "hidden": true, + "id": "text3866985172", + "max": 0, + "min": 0, + "name": "sentTo", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": true, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": true, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": true, + "type": "autodate" + } + ], + "id": "pbc_1638494021", + "indexes": [ + "CREATE INDEX ` + "`" + `idx_otps_collectionRef_recordRef` + "`" + ` ON ` + "`" + `_otps` + "`" + ` (collectionRef, recordRef)" + ], + "listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "name": "_otps", + "system": true, + "type": "base", + "updateRule": null, + "viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" + }, + { + "createRule": null, + "deleteRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text455797646", + "max": 0, + "min": 0, + "name": "collectionRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text127846527", + "max": 0, + "min": 0, + "name": "recordRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text2462348188", + "max": 0, + "min": 0, + "name": "provider", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1044722854", + "max": 0, + "min": 0, + "name": "providerId", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": true, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": true, + "type": "autodate" + } + ], + "id": "pbc_2281828961", + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_externalAuths_record_provider` + "`" + ` ON ` + "`" + `_externalAuths` + "`" + ` (collectionRef, recordRef, provider)", + "CREATE UNIQUE INDEX ` + "`" + `idx_externalAuths_collection_provider` + "`" + ` ON ` + "`" + `_externalAuths` + "`" + ` (collectionRef, provider, providerId)" + ], + "listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "name": "_externalAuths", + "system": true, + "type": "base", + "updateRule": null, + "viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" + }, + { + "createRule": null, + "deleteRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text455797646", + "max": 0, + "min": 0, + "name": "collectionRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text127846527", + "max": 0, + "min": 0, + "name": "recordRef", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text4228609354", + "max": 0, + "min": 0, + "name": "fingerprint", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": true, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": true, + "type": "autodate" + } + ], + "id": "pbc_4275539003", + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_authOrigins_unique_pairs` + "`" + ` ON ` + "`" + `_authOrigins` + "`" + ` (collectionRef, recordRef, fingerprint)" + ], + "listRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId", + "name": "_authOrigins", + "system": true, + "type": "base", + "updateRule": null, + "viewRule": "@request.auth.id != '' && recordRef = @request.auth.id && collectionRef = @request.auth.collectionId" + }, + { + "authAlert": { + "emailTemplate": { + "body": "

    Hello,

    \n

    We noticed a login to your {APP_NAME} account from a new location.

    \n

    If this was you, you may disregard this email.

    \n

    If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Login from a new location" + }, + "enabled": true + }, + "authRule": "", + "authToken": { + "duration": 86400 + }, + "confirmEmailChangeTemplate": { + "body": "

    Hello,

    \n

    Click on the button below to confirm your new email address.

    \n

    \n Confirm new email\n

    \n

    If you didn't ask to change your email address, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Confirm your {APP_NAME} new email address" + }, + "createRule": null, + "deleteRule": null, + "emailChangeToken": { + "duration": 1800 + }, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "cost": 0, + "hidden": true, + "id": "password901924565", + "max": 0, + "min": 8, + "name": "password", + "pattern": "", + "presentable": false, + "required": true, + "system": true, + "type": "password" + }, + { + "autogeneratePattern": "[a-zA-Z0-9]{50}", + "hidden": true, + "id": "text2504183744", + "max": 60, + "min": 30, + "name": "tokenKey", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "exceptDomains": null, + "hidden": false, + "id": "email3885137012", + "name": "email", + "onlyDomains": null, + "presentable": false, + "required": true, + "system": true, + "type": "email" + }, + { + "hidden": false, + "id": "bool1547992806", + "name": "emailVisibility", + "presentable": false, + "required": false, + "system": true, + "type": "bool" + }, + { + "hidden": false, + "id": "bool256245529", + "name": "verified", + "presentable": false, + "required": false, + "system": true, + "type": "bool" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": true, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": true, + "type": "autodate" + } + ], + "fileToken": { + "duration": 180 + }, + "id": "pbc_3142635823", + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_tokenKey_pbc_3142635823` + "`" + ` ON ` + "`" + `_superusers` + "`" + ` (` + "`" + `tokenKey` + "`" + `)", + "CREATE UNIQUE INDEX ` + "`" + `idx_email_pbc_3142635823` + "`" + ` ON ` + "`" + `_superusers` + "`" + ` (` + "`" + `email` + "`" + `) WHERE ` + "`" + `email` + "`" + ` != ''" + ], + "listRule": null, + "manageRule": null, + "mfa": { + "duration": 1800, + "enabled": false, + "rule": "" + }, + "name": "_superusers", + "oauth2": { + "enabled": false, + "mappedFields": { + "avatarURL": "", + "id": "", + "name": "", + "username": "" + } + }, + "otp": { + "duration": 180, + "emailTemplate": { + "body": "

    Hello,

    \n

    Your one-time password is: {OTP}

    \n

    If you didn't ask for the one-time password, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "OTP for {APP_NAME}" + }, + "enabled": false, + "length": 8 + }, + "passwordAuth": { + "enabled": true, + "identityFields": [ + "email" + ] + }, + "passwordResetToken": { + "duration": 1800 + }, + "resetPasswordTemplate": { + "body": "

    Hello,

    \n

    Click on the button below to reset your password.

    \n

    \n Reset password\n

    \n

    If you didn't ask to reset your password, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Reset your {APP_NAME} password" + }, + "system": true, + "type": "auth", + "updateRule": null, + "verificationTemplate": { + "body": "

    Hello,

    \n

    Thank you for joining us at {APP_NAME}.

    \n

    Click on the button below to verify your email address.

    \n

    \n Verify\n

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Verify your {APP_NAME} email" + }, + "verificationToken": { + "duration": 259200 + }, + "viewRule": null + }, + { + "authAlert": { + "emailTemplate": { + "body": "

    Hello,

    \n

    We noticed a login to your {APP_NAME} account from a new location.

    \n

    If this was you, you may disregard this email.

    \n

    If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Login from a new location" + }, + "enabled": true + }, + "authRule": "", + "authToken": { + "duration": 604800 + }, + "confirmEmailChangeTemplate": { + "body": "

    Hello,

    \n

    Click on the button below to confirm your new email address.

    \n

    \n Confirm new email\n

    \n

    If you didn't ask to change your email address, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Confirm your {APP_NAME} new email address" + }, + "createRule": "", + "deleteRule": "id = @request.auth.id", + "emailChangeToken": { + "duration": 1800 + }, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "cost": 0, + "hidden": true, + "id": "password901924565", + "max": 0, + "min": 8, + "name": "password", + "pattern": "", + "presentable": false, + "required": true, + "system": true, + "type": "password" + }, + { + "autogeneratePattern": "[a-zA-Z0-9]{50}", + "hidden": true, + "id": "text2504183744", + "max": 60, + "min": 30, + "name": "tokenKey", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": true, + "type": "text" + }, + { + "exceptDomains": null, + "hidden": false, + "id": "email3885137012", + "name": "email", + "onlyDomains": null, + "presentable": false, + "required": true, + "system": true, + "type": "email" + }, + { + "hidden": false, + "id": "bool1547992806", + "name": "emailVisibility", + "presentable": false, + "required": false, + "system": true, + "type": "bool" + }, + { + "hidden": false, + "id": "bool256245529", + "name": "verified", + "presentable": false, + "required": false, + "system": true, + "type": "bool" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text1579384326", + "max": 255, + "min": 0, + "name": "name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "file376926767", + "maxSelect": 1, + "maxSize": 0, + "mimeTypes": [ + "image/jpeg", + "image/png", + "image/svg+xml", + "image/gif", + "image/webp" + ], + "name": "avatar", + "presentable": false, + "protected": false, + "required": false, + "system": false, + "thumbs": null, + "type": "file" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } + ], + "fileToken": { + "duration": 180 + }, + "id": "_pb_users_auth_", + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_tokenKey__pb_users_auth_` + "`" + ` ON ` + "`" + `users` + "`" + ` (` + "`" + `tokenKey` + "`" + `)", + "CREATE UNIQUE INDEX ` + "`" + `idx_email__pb_users_auth_` + "`" + ` ON ` + "`" + `users` + "`" + ` (` + "`" + `email` + "`" + `) WHERE ` + "`" + `email` + "`" + ` != ''" + ], + "listRule": "id = @request.auth.id", + "manageRule": null, + "mfa": { + "duration": 1800, + "enabled": false, + "rule": "" + }, + "name": "users", + "oauth2": { + "enabled": false, + "mappedFields": { + "avatarURL": "avatar", + "id": "", + "name": "name", + "username": "" + } + }, + "otp": { + "duration": 180, + "emailTemplate": { + "body": "

    Hello,

    \n

    Your one-time password is: {OTP}

    \n

    If you didn't ask for the one-time password, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "OTP for {APP_NAME}" + }, + "enabled": false, + "length": 8 + }, + "passwordAuth": { + "enabled": true, + "identityFields": [ + "email" + ] + }, + "passwordResetToken": { + "duration": 1800 + }, + "resetPasswordTemplate": { + "body": "

    Hello,

    \n

    Click on the button below to reset your password.

    \n

    \n Reset password\n

    \n

    If you didn't ask to reset your password, you can ignore this email.

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Reset your {APP_NAME} password" + }, + "system": false, + "type": "auth", + "updateRule": "id = @request.auth.id", + "verificationTemplate": { + "body": "

    Hello,

    \n

    Thank you for joining us at {APP_NAME}.

    \n

    Click on the button below to verify your email address.

    \n

    \n Verify\n

    \n

    \n Thanks,
    \n {APP_NAME} team\n

    ", + "subject": "Verify your {APP_NAME} email" + }, + "verificationToken": { + "duration": 259200 + }, + "viewRule": "id = @request.auth.id" + }, + { + "createRule": null, + "deleteRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "hidden": false, + "id": "text724990059", + "max": 0, + "min": 0, + "name": "title", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": false, + "type": "text" + }, + { + "convertURLs": false, + "hidden": false, + "id": "editor1843675174", + "maxSize": 0, + "name": "description", + "presentable": false, + "required": true, + "system": false, + "type": "editor" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } + ], + "id": "pbc_2602490748", + "indexes": [], + "listRule": null, + "name": "tasks", + "system": false, + "type": "base", + "updateRule": null, + "viewRule": null + } + ]` + + return app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false) + }, func(app core.App) error { + return nil + }) +} diff --git a/internal/models/tasks.go b/internal/models/tasks.go index 758b6ef..0f59fc9 100644 --- a/internal/models/tasks.go +++ b/internal/models/tasks.go @@ -4,7 +4,6 @@ type Task struct { Id string `json:"id"` Title string `json:"title"` Description string `json:"description"` - OwnerId string `json:"owner_id"` } var EmptyTask = Task{} diff --git a/internal/models/users.go b/internal/models/users.go deleted file mode 100644 index c885856..0000000 --- a/internal/models/users.go +++ /dev/null @@ -1,15 +0,0 @@ -package models - -import "strings" - -type User struct { - ID string `json:"id"` - Username string `json:"username"` - PswdHash string `json:"pswd_hash"` -} - -var EmptyUser = User{} - -func GenerateNameFromEmail(email string) string { - return strings.Split(email, "@")[0] -} diff --git a/internal/repositories/database/database.go b/internal/repositories/database/database.go index 702cb2f..58b6ec2 100644 --- a/internal/repositories/database/database.go +++ b/internal/repositories/database/database.go @@ -1,110 +1,73 @@ package database import ( - "database/sql" - "goth/internal/config" "goth/internal/models" - _ "modernc.org/sqlite" + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/core" ) type database struct { - conn *sql.DB + app core.App } -func NewDatabaseRepo(cfg config.ServerConfigs) (Database, error) { - conn, err := sql.Open("sqlite", cfg.DataBaseURL) - if err != nil { - return nil, err - } - - return &database{conn}, nil -} - -func (db *database) Close() error { - return db.conn.Close() +func NewDatabaseRepo(app core.App) database { + return database{app} } func (db *database) CreateTask(task models.Task) error { - query := `INSERT INTO tasks (id, title, description, owner_id) VALUES (?, ?, ?, ?)` - _, err := db.conn.Exec(query, task.Id, task.Title, task.Description, task.OwnerId) + query := `INSERT INTO tasks (id, title, description) VALUES ({:Id}, {:Title}, {:Description})` + q := db.app.DB().NewQuery(query).Bind(dbx.Params{ + "Id": task.Id, + "Title": task.Title, + "Description": task.Description, + }) + + _, err := q.Execute() return err } -func (db *database) ListTasksByOwner(ownerId string) ([]models.Task, error) { - query := `SELECT id, title, owner_id, description FROM tasks WHERE owner_id = ?` - rows, err := db.conn.Query(query, ownerId) - if err != nil { - return nil, err - } +func (db *database) ListTasks() ([]models.Task, error) { + query := `SELECT id, title, description FROM tasks` + q := db.app.DB().NewQuery(query) - var tasks []models.Task - for rows.Next() { - var task models.Task - err := rows.Scan(&task.Id, &task.Title, &task.OwnerId, &task.Description) - if err != nil { - return nil, err - } - - tasks = append(tasks, task) + tasks := make([]models.Task, 0) + if err := q.All(&tasks); err != nil { + return nil, err } return tasks, nil } func (db *database) RetrieveTaskById(taskId string) (models.Task, error) { - query := `SELECT id, title, owner_id, description FROM tasks WHERE id = ?` - row := db.conn.QueryRow(query, taskId) + query := `SELECT id, title, description FROM tasks WHERE id = {:Id}` + q := db.app.DB().NewQuery(query).Bind(dbx.Params{ + "Id": taskId, + }) var task models.Task - err := row.Scan(&task.Id, &task.Title, &task.OwnerId, &task.Description) - if err != nil { - return models.EmptyTask, err - } - - return task, nil + err := q.One(&task) + return task, err } func (db *database) DeleteTask(taskId string) error { - query := `DELETE FROM tasks WHERE id = ?` - _, err := db.conn.Exec(query, taskId) - return err -} + query := `DELETE FROM tasks WHERE id = {:Id}` + q := db.app.DB().NewQuery(query).Bind(dbx.Params{ + "Id": taskId, + }) -func (db *database) UpdateTask(task models.Task) error { - query := `UPDATE tasks SET title = ?, description = ? WHERE id = ?` - _, err := db.conn.Exec(query, task.Title, task.Description, task.Id) + _, err := q.Execute() return err } -func (db *database) InsertUser(user models.User) error { - query := `INSERT INTO users (id, username, pswd_hash) VALUES (?, ?, ?)` - _, err := db.conn.Exec(query, user.ID, user.Username, user.PswdHash) +func (db *database) UpdateTask(task models.Task) error { + query := `UPDATE tasks SET title = {:Title}, description = {:Description} WHERE id = {:Id}` + q := db.app.DB().NewQuery(query).Bind(dbx.Params{ + "Id": task.Id, + "Title": task.Title, + "Description": task.Description, + }) + + _, err := q.Execute() return err } - -func (db *database) RetrieveUserByName(username string) (models.User, error) { - query := `SELECT id, username, pswd_hash FROM users WHERE username = ?` - row := db.conn.QueryRow(query, username) - - var user models.User - err := row.Scan(&user.ID, &user.Username, &user.PswdHash) - if err != nil { - return models.EmptyUser, err - } - - return user, nil -} - -func (db *database) RetrieveUserById(id string) (models.User, error) { - query := `SELECT id, username, pswd_hash FROM users WHERE id = ?` - row := db.conn.QueryRow(query, id) - - var user models.User - err := row.Scan(&user.ID, &user.Username, &user.PswdHash) - if err != nil { - return models.EmptyUser, err - } - - return user, nil -} diff --git a/internal/repositories/database/fake.go b/internal/repositories/database/fake.go index b703246..ce4b1a5 100644 --- a/internal/repositories/database/fake.go +++ b/internal/repositories/database/fake.go @@ -2,7 +2,6 @@ package database import ( "database/sql" - "fmt" "goth/internal/models" _ "modernc.org/sqlite" @@ -10,13 +9,11 @@ import ( type fakeDatabase struct { tasks map[string]models.Task - users map[string]models.User } func NewFakeDatabaseRepo() (Database, error) { return &fakeDatabase{ tasks: make(map[string]models.Task), - users: make(map[string]models.User), }, nil } @@ -29,13 +26,11 @@ func (db *fakeDatabase) CreateTask(task models.Task) error { return nil } -func (db *fakeDatabase) ListTasksByOwner(ownerId string) ([]models.Task, error) { +func (db *fakeDatabase) ListTasks() ([]models.Task, error) { tasks := make([]models.Task, 0) for _, task := range db.tasks { - if task.OwnerId == ownerId { - tasks = append(tasks, task) - } + tasks = append(tasks, task) } return tasks, nil @@ -59,32 +54,3 @@ func (db *fakeDatabase) UpdateTask(task models.Task) error { db.tasks[task.Id] = task return nil } - -func (db *fakeDatabase) InsertUser(user models.User) error { - _, ok := db.users[user.ID] - if ok { - return fmt.Errorf("non unique email") - } - - db.users[user.ID] = user - return nil -} - -func (db *fakeDatabase) RetrieveUserById(id string) (models.User, error) { - user, ok := db.users[id] - if !ok { - return models.EmptyUser, sql.ErrNoRows - } - - return user, nil -} - -func (db *fakeDatabase) RetrieveUserByName(username string) (models.User, error) { - for _, u := range db.users { - if u.Username == username { - return u, nil - } - } - - return models.EmptyUser, sql.ErrNoRows -} diff --git a/internal/repositories/database/interface.go b/internal/repositories/database/interface.go index 459501b..9d540a2 100644 --- a/internal/repositories/database/interface.go +++ b/internal/repositories/database/interface.go @@ -13,9 +13,5 @@ type Database interface { RetrieveTaskById(taskId string) (models.Task, error) UpdateTask(task models.Task) error DeleteTask(taskId string) error - ListTasksByOwner(ownerId string) ([]models.Task, error) - - InsertUser(user models.User) error - RetrieveUserById(id string) (models.User, error) - RetrieveUserByName(username string) (models.User, error) + ListTasks() ([]models.Task, error) } diff --git a/internal/repositories/jwt/interface.go b/internal/repositories/jwt/interface.go deleted file mode 100644 index 723ca49..0000000 --- a/internal/repositories/jwt/interface.go +++ /dev/null @@ -1,15 +0,0 @@ -package jwt - -import ( - "time" -) - -// type JWTClaims struct { -// Id string `json:"id"` -// gojwt.RegisteredClaims -// } - -type JWT interface { - GenerateJWT(sub string, expiration time.Time) (string, error) - ParseJWT(token string) (string, error) -} diff --git a/internal/repositories/jwt/jwt.go b/internal/repositories/jwt/jwt.go deleted file mode 100644 index fbd1582..0000000 --- a/internal/repositories/jwt/jwt.go +++ /dev/null @@ -1,47 +0,0 @@ -package jwt - -import ( - "goth/internal/config" - "time" - - gojwt "github.com/golang-jwt/jwt/v5" -) - -type jwt struct { - bsecret []byte -} - -func NewJWTRepo(cfg config.ServerConfigs) JWT { - return &jwt{ - bsecret: []byte(cfg.JwtSecret), - } -} - -func (j *jwt) GenerateJWT(sub string, expiration time.Time) (string, error) { - claims := &gojwt.RegisteredClaims{ - Subject: sub, - IssuedAt: gojwt.NewNumericDate(time.Now()), - ExpiresAt: gojwt.NewNumericDate(expiration), - } - - jwtoken := gojwt.NewWithClaims(gojwt.SigningMethodHS256, claims) - return jwtoken.SignedString(j.bsecret) -} - -func (j *jwt) ParseJWT(token string) (string, error) { - claims := gojwt.RegisteredClaims{} - - _, err := gojwt.ParseWithClaims(token, &claims, func(token *gojwt.Token) (interface{}, error) { - return j.bsecret, nil - }) - - if err != nil { - return "", err - } - - if claims.Subject == "" { - return "", gojwt.ErrInvalidKey - } - - return claims.Subject, nil -} diff --git a/internal/routes/errors.go b/internal/routes/errors.go deleted file mode 100644 index 91d6a3f..0000000 --- a/internal/routes/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package routes - -import ( - "fmt" - "goth/internal/components" - - "github.com/gofiber/fiber/v2" -) - -func ErrorHandler(c *fiber.Ctx, err error) error { - c.SendStatus(fiber.StatusInternalServerError) - fmt.Printf("Route Error [%s]: %v\n", c.Path(), err) - return sendPage(c, components.ErrorPage()) -} - -func notFoundHandler(c *fiber.Ctx) error { - c.SendStatus(fiber.StatusNotFound) - return sendPage(c, components.NotFoundPage()) -} diff --git a/internal/routes/init.go b/internal/routes/init.go deleted file mode 100644 index cf794a5..0000000 --- a/internal/routes/init.go +++ /dev/null @@ -1,46 +0,0 @@ -package routes - -import ( - "fmt" - "goth/internal/config" - "goth/internal/controllers" - "goth/internal/repositories/database" - "goth/internal/repositories/jwt" - - "github.com/gofiber/fiber/v2" -) - -func healthzHandler(c *fiber.Ctx) error { - return c.SendStatus(fiber.StatusOK) -} - -func Init(app *fiber.App, cfg config.ServerConfigs) error { - jwtRepo := jwt.NewJWTRepo(cfg) - - dbRepo, err := database.NewDatabaseRepo(cfg) - if err != nil { - return fmt.Errorf("[Init] failed to get database: %w", err) - } - - uc := controllers.NewUserController(dbRepo, jwtRepo) - tc := controllers.NewTaskController(dbRepo) - - app.Get("/auth/login", getLoginHandler) - app.Post("/auth/login", postLoginHandler(uc)) - - app.Get("/auth/register", getRegisterHandler) - app.Post("/auth/register", postRegisterHandler(uc)) - - app.Get("/auth/logout", getLogoutHandler) - - app.Get("/", withAuth(uc, tc, taskList)) - app.Get("/new", withAuth(uc, tc, taskNew)) - app.Get("/edit/:id", withAuth(uc, tc, taskEdit)) - app.Post("/edit/:id", withAuth(uc, tc, taskSave)) - - app.Use("/statics", staticsHandler) - app.Use("/healthz", healthzHandler) - app.Use(notFoundHandler) - - return nil -} diff --git a/internal/routes/render.go b/internal/routes/render.go index c1cb868..2a290fd 100644 --- a/internal/routes/render.go +++ b/internal/routes/render.go @@ -1,13 +1,19 @@ package routes import ( + "bytes" "context" + "net/http" "github.com/a-h/templ" - "github.com/gofiber/fiber/v2" + "github.com/pocketbase/pocketbase/core" ) -func sendPage(c *fiber.Ctx, page templ.Component) error { - c.Set("Content-Type", "text/html") - return page.Render(context.Background(), c) +func sendPage(e *core.RequestEvent, page templ.Component) error { + buf := new(bytes.Buffer) + if err := page.Render(context.Background(), buf); err != nil { + return err + } + + return e.HTML(http.StatusOK, buf.String()) } diff --git a/internal/routes/setup.go b/internal/routes/setup.go new file mode 100644 index 0000000..ddd82de --- /dev/null +++ b/internal/routes/setup.go @@ -0,0 +1,27 @@ +package routes + +import ( + "goth/internal/embeded" + + "github.com/pocketbase/pocketbase/core" + + "github.com/pocketbase/pocketbase/apis" +) + +var assetsHandler = apis.Static(embeded.Assets, true) + +func healthzHandler(e *core.RequestEvent) error { + return e.String(200, "ok") +} + +func SetupRoutes(se *core.ServeEvent) error { + se.Router.GET("/", taskList) + se.Router.GET("/new", taskNew) + se.Router.GET("/edit/{id}", taskEdit) + se.Router.POST("/edit/{id}", taskSave) + + se.Router.GET("/healthz", healthzHandler) + se.Router.GET("/statics/{path...}", assetsHandler) + + return se.Next() +} diff --git a/internal/routes/statics.go b/internal/routes/statics.go deleted file mode 100644 index 325c09d..0000000 --- a/internal/routes/statics.go +++ /dev/null @@ -1,14 +0,0 @@ -package routes - -import ( - "goth/internal/embeded" - "net/http" - - "github.com/gofiber/fiber/v2/middleware/filesystem" -) - -var staticsHandler = filesystem.New(filesystem.Config{ - Root: http.FS(embeded.Statics), - PathPrefix: "statics", - MaxAge: 60 * 60, -}) diff --git a/internal/routes/tasks.go b/internal/routes/tasks.go index 02f33d5..5bc8ec8 100644 --- a/internal/routes/tasks.go +++ b/internal/routes/tasks.go @@ -1,53 +1,89 @@ package routes import ( + "errors" "goth/internal/components" - "goth/internal/controllers" "goth/internal/models" + "goth/internal/repositories/database" - "github.com/gofiber/fiber/v2" + "github.com/pocketbase/pocketbase/core" ) -func taskList(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - tasks, err := tc.ListTasks(user.ID) +func taskList(e *core.RequestEvent) error { + db := database.NewDatabaseRepo(e.App) + tasks, err := db.ListTasks() if err != nil { return err } - return sendPage(c, components.TaskListPage(tasks)) + return sendPage(e, components.TaskListPage(tasks)) } -func taskNew(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - task, err := tc.CreateTask(user.ID) - if err != nil { +func taskNew(e *core.RequestEvent) error { + db := database.NewDatabaseRepo(e.App) + + task := models.Task{ + Id: models.GenerateId(), + Title: "New Task", + Description: "New Task Description", + } + + if err := db.CreateTask(task); err != nil { return err } - return c.Redirect("/edit/" + task.Id) + return e.Redirect(302, "/edit/"+task.Id) } -func taskEdit(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - taskId := c.Params("id") - task, err := tc.RetrieveTask(user.ID, taskId) +func taskEdit(e *core.RequestEvent) error { + taskId := e.Request.PathValue("id") + if taskId == "" { + return errors.New("task id is required") + } + + db := database.NewDatabaseRepo(e.App) + task, err := db.RetrieveTaskById(taskId) if err != nil { return err } - return sendPage(c, components.TaskEditPage(task)) + return sendPage(e, components.TaskEditPage(task)) +} + +type TaskChange struct { + Title string `json:"title" form:"title"` + Description string `json:"description" form:"description"` } -func taskSave(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - var taskId = c.Params("id") - var taskChange controllers.TaskChange - err := c.BodyParser(&taskChange) +func taskSave(e *core.RequestEvent) error { + taskId := e.Request.PathValue("id") + if taskId == "" { + return errors.New("task id is required") + } + + var changes TaskChange + if err := e.BindBody(&changes); err != nil { + return err + } + + db := database.NewDatabaseRepo(e.App) + task, err := db.RetrieveTaskById(taskId) if err != nil { return err } - if err := tc.UpdateTask(user.ID, taskId, taskChange); err != nil { + if changes.Title != "" { + task.Title = changes.Title + } + + if changes.Description != "" { + task.Description = changes.Description + } + + if err := db.UpdateTask(task); err != nil { return err } - return c.SendStatus(fiber.StatusOK) + return e.String(200, "ok") } diff --git a/internal/routes/users.go b/internal/routes/users.go deleted file mode 100644 index aa30857..0000000 --- a/internal/routes/users.go +++ /dev/null @@ -1,81 +0,0 @@ -package routes - -import ( - "goth/internal/components" - "goth/internal/controllers" - "goth/internal/models" - "goth/internal/utils" - - "github.com/gofiber/fiber/v2" -) - -const cookieName = "goth:jwt" - -func withAuth[C controllers.Controllers](uController *controllers.UserController, controller *C, handler func(*C, *fiber.Ctx, models.User) error) fiber.Handler { - return func(ctx *fiber.Ctx) error { - jwt := ctx.Cookies(cookieName) - if jwt == "" { - return ctx.Redirect("/auth/login") - } - - user, err := uController.VerifyJWTCookie(jwt) - if err != nil { - return ctx.Redirect("/auth/login") - } - - return handler(controller, ctx, user) - } -} - -func getLogoutHandler(c *fiber.Ctx) error { - utils.ClearCookie(c, cookieName) - return sendPage(c, components.PostLogoutPage()) -} - -func getRegisterHandler(c *fiber.Ctx) error { - utils.ClearCookie(c, cookieName) - return sendPage(c, components.RegisterPage()) -} - -func getLoginHandler(c *fiber.Ctx) error { - utils.ClearCookie(c, cookieName) - return sendPage(c, components.LoginPage()) -} - -func postLoginHandler(uc *controllers.UserController) fiber.Handler { - return func(c *fiber.Ctx) error { - var req controllers.UserRequest - err := c.BodyParser(&req) - if err != nil { - return err - } - - cookie, err := uc.Login(req, cookieName) - if err != nil { - return err - } - - c.Cookie(cookie) - c.Set("HX-Redirect", "/") - return c.SendStatus(fiber.StatusOK) - } -} - -func postRegisterHandler(uc *controllers.UserController) fiber.Handler { - return func(c *fiber.Ctx) error { - var req controllers.UserRequest - err := c.BodyParser(&req) - if err != nil { - return err - } - - cookie, err := uc.Register(req, cookieName) - if err != nil { - return err - } - - c.Cookie(cookie) - c.Set("HX-Redirect", "/") - return c.SendStatus(fiber.StatusOK) - } -} diff --git a/internal/utils/cookie.go b/internal/utils/cookie.go deleted file mode 100644 index cb0a167..0000000 --- a/internal/utils/cookie.go +++ /dev/null @@ -1,27 +0,0 @@ -package utils - -import ( - "time" - - "github.com/gofiber/fiber/v2" -) - -func SetCookie(c *fiber.Ctx, name string, value string, expiration time.Time) { - c.Cookie(buildCookie(name, value, expiration)) -} - -func ClearCookie(c *fiber.Ctx, name string) { - // c.ClearCookie(name) does not work - https://github.com/gofiber/fiber/issues/1127 - c.Cookie(buildCookie(name, "", time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))) -} - -func buildCookie(name string, value string, expires time.Time) *fiber.Cookie { - cookie := new(fiber.Cookie) - cookie.Name = name - cookie.Value = value - cookie.Expires = expires - // cookie.HTTPOnly = true - // cookie.Path = "/api/v1/auth/" - // cookie.Domain = "example.com" - return cookie -} diff --git a/internal/utils/password.go b/internal/utils/password.go deleted file mode 100644 index f5a443e..0000000 --- a/internal/utils/password.go +++ /dev/null @@ -1,13 +0,0 @@ -package utils - -import "golang.org/x/crypto/bcrypt" - -func HashPassword(password string) (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) - return string(bytes), err -} - -func CheckPasswordHash(password, hash string) bool { - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - return err == nil -} diff --git a/internal/utils/request.go b/internal/utils/request.go deleted file mode 100644 index 6acec7c..0000000 --- a/internal/utils/request.go +++ /dev/null @@ -1,53 +0,0 @@ -package utils - -import ( - "bytes" - "errors" - "fmt" - "io" - "net/http" - "time" -) - -type Headers map[string]string - -type Response struct { - StatusCode int - Body []byte -} - -type HTTPClient struct { - Headers Headers - BaseUrl string -} - -func (c *HTTPClient) Request(method string, path string, body []byte) (Response, error) { - url := fmt.Sprintf("%s%s", c.BaseUrl, path) - bodyReader := bytes.NewReader(body) - - req, err := http.NewRequest(method, url, bodyReader) - if err != nil { - return Response{}, errors.New("failed to create request: " + err.Error()) - } - - for key, value := range c.Headers { - req.Header.Set(key, value) - } - - client := http.Client{Timeout: 30 * time.Second} - - res, err := client.Do(req) - if err != nil { - return Response{}, errors.New("failed to perform request: " + err.Error()) - } - - rawBody, err := io.ReadAll(res.Body) - if err != nil { - return Response{}, errors.New("could not read response body: " + err.Error()) - } - - return Response{ - StatusCode: res.StatusCode, - Body: rawBody, - }, nil -} diff --git a/main.go b/main.go index c46b1d3..bc63609 100644 --- a/main.go +++ b/main.go @@ -5,5 +5,5 @@ import ( ) func main() { - cmd.Execute() + cmd.Root() } diff --git a/migrations/.gitignore b/migrations/.gitignore new file mode 100644 index 0000000..472fecd --- /dev/null +++ b/migrations/.gitignore @@ -0,0 +1 @@ +*.go \ No newline at end of file