From d1996267d4fc4954d44e048f93d3a8ab57773e9f Mon Sep 17 00:00:00 2001 From: felipereyel Date: Sun, 27 Apr 2025 15:26:38 -0300 Subject: [PATCH 1/5] userless --- go.mod | 11 ++- go.sum | 22 +++-- internal/components/design.templ | 77 +--------------- internal/controllers/controllers.go | 5 -- internal/controllers/tasks.go | 21 ++--- internal/controllers/users.go | 89 ------------------- .../embeded/migrations/01_initial.down.sql | 1 - internal/embeded/migrations/01_initial.up.sql | 8 -- .../{02_tasks.down.sql => 01_tasks.down.sql} | 0 internal/embeded/migrations/01_tasks.up.sql | 8 ++ internal/embeded/migrations/02_tasks.up.sql | 10 --- internal/models/tasks.go | 1 - internal/models/users.go | 15 ---- internal/repositories/database/database.go | 48 ++-------- internal/repositories/database/fake.go | 38 +------- internal/repositories/database/interface.go | 6 +- internal/repositories/jwt/interface.go | 15 ---- internal/repositories/jwt/jwt.go | 47 ---------- internal/routes/bind.go | 15 ++++ internal/routes/init.go | 20 +---- internal/routes/tasks.go | 17 ++-- internal/routes/users.go | 81 ----------------- 22 files changed, 73 insertions(+), 482 deletions(-) delete mode 100644 internal/controllers/controllers.go delete mode 100644 internal/controllers/users.go delete mode 100644 internal/embeded/migrations/01_initial.down.sql delete mode 100644 internal/embeded/migrations/01_initial.up.sql rename internal/embeded/migrations/{02_tasks.down.sql => 01_tasks.down.sql} (100%) create mode 100644 internal/embeded/migrations/01_tasks.up.sql delete mode 100644 internal/embeded/migrations/02_tasks.up.sql delete mode 100644 internal/models/users.go delete mode 100644 internal/repositories/jwt/interface.go delete mode 100644 internal/repositories/jwt/jwt.go create mode 100644 internal/routes/bind.go delete mode 100644 internal/routes/users.go diff --git a/go.mod b/go.mod index baa7536..3939d7e 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,9 @@ 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/gofiber/fiber/v2 v2.52.6 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 @@ -24,17 +23,17 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // 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/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sqlite3 v1.14.17 // 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/fasthttp v1.51.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 diff --git a/go.sum b/go.sum index 223bff2..4b7a798 100644 --- a/go.sum +++ b/go.sum @@ -7,18 +7,16 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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/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/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= 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/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/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/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= @@ -30,8 +28,8 @@ 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/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 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= @@ -39,8 +37,8 @@ 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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= @@ -59,8 +57,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs 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/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= +github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= 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= diff --git a/internal/components/design.templ b/internal/components/design.templ index c4bf0b0..84389b7 100644 --- a/internal/components/design.templ +++ b/internal/components/design.templ @@ -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/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 index edfebc9..7e86286 100644 --- a/internal/controllers/tasks.go +++ b/internal/controllers/tasks.go @@ -18,21 +18,20 @@ func NewTaskController(dbRepo database.Database) *TaskController { } } -func (tc *TaskController) CreateTask(ownerId string) (models.Task, error) { +func (tc *TaskController) CreateTask() (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) ListTasks() ([]models.Task, error) { + return tc.DbRepo.ListTasks() } -func (tc *TaskController) RetrieveTask(ownerId, taskId string) (models.Task, error) { +func (tc *TaskController) RetrieveTask(taskId string) (models.Task, error) { task, err := tc.DbRepo.RetrieveTaskById(taskId) if err != nil { if err == sql.ErrNoRows { @@ -42,15 +41,11 @@ func (tc *TaskController) RetrieveTask(ownerId, taskId string) (models.Task, err 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) +func (tc *TaskController) DeleteTask(taskId string) error { + _, err := tc.RetrieveTask(taskId) if err != nil { return err } @@ -63,8 +58,8 @@ type TaskChange struct { Description string `json:"description"` } -func (tc *TaskController) UpdateTask(ownerId, taskId string, changes TaskChange) error { - task, err := tc.RetrieveTask(ownerId, taskId) +func (tc *TaskController) UpdateTask(taskId string, changes TaskChange) error { + task, err := tc.RetrieveTask(taskId) if err != nil { return err } 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/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/01_tasks.down.sql similarity index 100% rename from internal/embeded/migrations/02_tasks.down.sql rename to internal/embeded/migrations/01_tasks.down.sql diff --git a/internal/embeded/migrations/01_tasks.up.sql b/internal/embeded/migrations/01_tasks.up.sql new file mode 100644 index 0000000..edd8b4c --- /dev/null +++ b/internal/embeded/migrations/01_tasks.up.sql @@ -0,0 +1,8 @@ +-- SQLITE + +CREATE TABLE tasks ( + id UUID PRIMARY KEY, + title TEXT NOT NULL, + description TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); 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/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..e4659ec 100644 --- a/internal/repositories/database/database.go +++ b/internal/repositories/database/database.go @@ -26,14 +26,14 @@ func (db *database) Close() error { } 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 (?, ?, ?)` + _, err := db.conn.Exec(query, task.Id, task.Title, task.Description) 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) +func (db *database) ListTasks() ([]models.Task, error) { + query := `SELECT id, title, description FROM tasks` + rows, err := db.conn.Query(query) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func (db *database) ListTasksByOwner(ownerId string) ([]models.Task, error) { var tasks []models.Task for rows.Next() { var task models.Task - err := rows.Scan(&task.Id, &task.Title, &task.OwnerId, &task.Description) + err := rows.Scan(&task.Id, &task.Title, &task.Description) if err != nil { return nil, err } @@ -53,11 +53,11 @@ func (db *database) ListTasksByOwner(ownerId string) ([]models.Task, error) { } func (db *database) RetrieveTaskById(taskId string) (models.Task, error) { - query := `SELECT id, title, owner_id, description FROM tasks WHERE id = ?` + query := `SELECT id, title, description FROM tasks WHERE id = ?` row := db.conn.QueryRow(query, taskId) var task models.Task - err := row.Scan(&task.Id, &task.Title, &task.OwnerId, &task.Description) + err := row.Scan(&task.Id, &task.Title, &task.Description) if err != nil { return models.EmptyTask, err } @@ -76,35 +76,3 @@ func (db *database) UpdateTask(task models.Task) error { _, err := db.conn.Exec(query, task.Title, task.Description, task.Id) 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) - 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/bind.go b/internal/routes/bind.go new file mode 100644 index 0000000..2e421e5 --- /dev/null +++ b/internal/routes/bind.go @@ -0,0 +1,15 @@ +package routes + +import ( + "goth/internal/controllers" + + "github.com/gofiber/fiber/v2" +) + +const cookieName = "goth:jwt" + +func controllerBind(controller *controllers.TaskController, handler func(*controllers.TaskController, *fiber.Ctx) error) fiber.Handler { + return func(ctx *fiber.Ctx) error { + return handler(controller, ctx) + } +} diff --git a/internal/routes/init.go b/internal/routes/init.go index cf794a5..f6ced97 100644 --- a/internal/routes/init.go +++ b/internal/routes/init.go @@ -5,7 +5,6 @@ import ( "goth/internal/config" "goth/internal/controllers" "goth/internal/repositories/database" - "goth/internal/repositories/jwt" "github.com/gofiber/fiber/v2" ) @@ -15,28 +14,17 @@ func healthzHandler(c *fiber.Ctx) error { } 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.Get("/", controllerBind(tc, taskList)) + app.Get("/new", controllerBind(tc, taskNew)) + app.Get("/edit/:id", controllerBind(tc, taskEdit)) + app.Post("/edit/:id", controllerBind(tc, taskSave)) app.Use("/statics", staticsHandler) app.Use("/healthz", healthzHandler) diff --git a/internal/routes/tasks.go b/internal/routes/tasks.go index 02f33d5..f6ce150 100644 --- a/internal/routes/tasks.go +++ b/internal/routes/tasks.go @@ -3,13 +3,12 @@ package routes import ( "goth/internal/components" "goth/internal/controllers" - "goth/internal/models" "github.com/gofiber/fiber/v2" ) -func taskList(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - tasks, err := tc.ListTasks(user.ID) +func taskList(tc *controllers.TaskController, c *fiber.Ctx) error { + tasks, err := tc.ListTasks() if err != nil { return err } @@ -17,8 +16,8 @@ func taskList(tc *controllers.TaskController, c *fiber.Ctx, user models.User) er return sendPage(c, components.TaskListPage(tasks)) } -func taskNew(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { - task, err := tc.CreateTask(user.ID) +func taskNew(tc *controllers.TaskController, c *fiber.Ctx) error { + task, err := tc.CreateTask() if err != nil { return err } @@ -26,9 +25,9 @@ func taskNew(tc *controllers.TaskController, c *fiber.Ctx, user models.User) err return c.Redirect("/edit/" + task.Id) } -func taskEdit(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { +func taskEdit(tc *controllers.TaskController, c *fiber.Ctx) error { taskId := c.Params("id") - task, err := tc.RetrieveTask(user.ID, taskId) + task, err := tc.RetrieveTask(taskId) if err != nil { return err @@ -37,7 +36,7 @@ func taskEdit(tc *controllers.TaskController, c *fiber.Ctx, user models.User) er return sendPage(c, components.TaskEditPage(task)) } -func taskSave(tc *controllers.TaskController, c *fiber.Ctx, user models.User) error { +func taskSave(tc *controllers.TaskController, c *fiber.Ctx) error { var taskId = c.Params("id") var taskChange controllers.TaskChange err := c.BodyParser(&taskChange) @@ -45,7 +44,7 @@ func taskSave(tc *controllers.TaskController, c *fiber.Ctx, user models.User) er return err } - if err := tc.UpdateTask(user.ID, taskId, taskChange); err != nil { + if err := tc.UpdateTask(taskId, taskChange); err != nil { return err } 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) - } -} From ea09b4be6c776a6b9cb4ee284570b56058dbf166 Mon Sep 17 00:00:00 2001 From: felipereyel Date: Sun, 27 Apr 2025 15:29:16 -0300 Subject: [PATCH 2/5] jo jwt --- .env.example | 1 - internal/config/config.go | 6 ------ 2 files changed, 7 deletions(-) diff --git a/.env.example b/.env.example index e7a0bf9..908fcab 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,3 @@ AUTO_MIGRATE=true # Mandatory DATABASE_URL=db.sqlite -JWT_SECRET=secrettt diff --git a/internal/config/config.go b/internal/config/config.go index 0113ef7..2ecd3af 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,7 +11,6 @@ type ServerConfigs struct { ServerAddress string DataBaseURL string - JwtSecret string } func GetServerConfigs() ServerConfigs { @@ -24,11 +23,6 @@ func GetServerConfigs() ServerConfigs { 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") From 4b6744ac6c37cde18d801c7221fb87ac44485dbf Mon Sep 17 00:00:00 2001 From: felipereyel Date: Sun, 27 Apr 2025 16:30:39 -0300 Subject: [PATCH 3/5] v0 working: next migrations --- .gitignore | 1 + Makefile | 4 +- README.md | 4 +- go.mod | 61 +++--- go.sum | 190 ++++++++++-------- internal/cmd/migrate/migrate.go | 56 ------ internal/cmd/root.go | 47 ++--- internal/cmd/server/server.go | 33 --- internal/components/design.templ | 6 +- internal/controllers/tasks.go | 76 ------- internal/embeded/.gitignore | 1 + internal/embeded/{statics.go => assets.go} | 4 +- internal/embeded/migrations.go | 8 - internal/embeded/migrations/01_tasks.down.sql | 1 - internal/embeded/migrations/01_tasks.up.sql | 8 - internal/embeded/statics/favicon.ico | Bin 15406 -> 0 bytes internal/repositories/database/database.go | 79 ++++---- internal/routes/bind.go | 15 -- internal/routes/errors.go | 19 -- internal/routes/init.go | 34 ---- internal/routes/render.go | 14 +- internal/routes/setup.go | 27 +++ internal/routes/statics.go | 14 -- internal/routes/tasks.go | 75 +++++-- internal/utils/cookie.go | 27 --- internal/utils/password.go | 13 -- internal/utils/request.go | 53 ----- main.go | 2 +- 28 files changed, 291 insertions(+), 581 deletions(-) delete mode 100644 internal/cmd/migrate/migrate.go delete mode 100644 internal/cmd/server/server.go delete mode 100644 internal/controllers/tasks.go create mode 100644 internal/embeded/.gitignore rename internal/embeded/{statics.go => assets.go} (57%) delete mode 100644 internal/embeded/migrations.go delete mode 100644 internal/embeded/migrations/01_tasks.down.sql delete mode 100644 internal/embeded/migrations/01_tasks.up.sql delete mode 100644 internal/embeded/statics/favicon.ico delete mode 100644 internal/routes/bind.go delete mode 100644 internal/routes/errors.go delete mode 100644 internal/routes/init.go create mode 100644 internal/routes/setup.go delete mode 100644 internal/routes/statics.go delete mode 100644 internal/utils/cookie.go delete mode 100644 internal/utils/password.go delete mode 100644 internal/utils/request.go 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/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..a64f362 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 diff --git a/go.mod b/go.mod index 3939d7e..c9481f6 100644 --- a/go.mod +++ b/go.mod @@ -6,47 +6,44 @@ toolchain go1.24.1 require ( github.com/a-h/templ v0.3.857 - github.com/gofiber/fiber/v2 v2.52.6 - github.com/golang-migrate/migrate/v4 v4.16.2 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.9 // 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.16 // 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.51.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 4b7a798..cfb782d 100644 --- a/go.sum +++ b/go.sum @@ -1,108 +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.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= -github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= -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/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/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/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.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/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.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= -github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= -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..729618f 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -1,45 +1,22 @@ package cmd import ( - "goth/internal/cmd/migrate" - "goth/internal/cmd/server" + "goth/internal/routes" + "log" - "github.com/spf13/cobra" + "github.com/pocketbase/pocketbase" ) -var rootCmd = &cobra.Command{ - Use: "goth", - Short: "goth app CLI", -} - -var serveCmd = &cobra.Command{ - Use: "serve", - Short: "Serve the application", - Run: server.Serve, -} - -var migrateUpCmd = &cobra.Command{ - Use: "migrate:up", - Short: "Migrates Up the database", - Run: migrate.Up, -} - -var migrateDownCmd = &cobra.Command{ - Use: "migrate:down", - Short: "Migrates Down the database", - Run: migrate.Down, - Args: cobra.ExactArgs(1), -} +func Root() { + app := pocketbase.New() + app.OnServe().BindFunc(routes.SetupRoutes) -func init() { - rootCmd.AddCommand(serveCmd) - rootCmd.AddCommand(migrateUpCmd) - rootCmd.AddCommand(migrateDownCmd) -} + // cfg := config.GetServerConfigs() + // if cfg.AutoMigrate { + // migrations.Register() + // } -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 84389b7..a1204ab 100644 --- a/internal/components/design.templ +++ b/internal/components/design.templ @@ -14,9 +14,9 @@ templ rawPage(title string) { { title } | GOTH - - - + + + { children... } diff --git a/internal/controllers/tasks.go b/internal/controllers/tasks.go deleted file mode 100644 index 7e86286..0000000 --- a/internal/controllers/tasks.go +++ /dev/null @@ -1,76 +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() (models.Task, error) { - task := models.Task{ - Id: models.GenerateId(), - Title: "New Task", - Description: "New Task Description", - } - return task, tc.DbRepo.CreateTask(task) -} - -func (tc *TaskController) ListTasks() ([]models.Task, error) { - return tc.DbRepo.ListTasks() -} - -func (tc *TaskController) RetrieveTask(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 - } - - return task, nil -} - -func (tc *TaskController) DeleteTask(taskId string) error { - _, err := tc.RetrieveTask(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(taskId string, changes TaskChange) error { - task, err := tc.RetrieveTask(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/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_tasks.down.sql b/internal/embeded/migrations/01_tasks.down.sql deleted file mode 100644 index 87f1ed5..0000000 --- a/internal/embeded/migrations/01_tasks.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE tasks; diff --git a/internal/embeded/migrations/01_tasks.up.sql b/internal/embeded/migrations/01_tasks.up.sql deleted file mode 100644 index edd8b4c..0000000 --- a/internal/embeded/migrations/01_tasks.up.sql +++ /dev/null @@ -1,8 +0,0 @@ --- SQLITE - -CREATE TABLE tasks ( - id UUID PRIMARY KEY, - title TEXT NOT NULL, - description TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/internal/embeded/statics/favicon.ico b/internal/embeded/statics/favicon.ico deleted file mode 100644 index 2f23f5077367fa60587929a8ee12776ab0d377bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeI0ZERKL9l%dtq@|@$3J4R_0xc8~sERNZu}mn1Qc-knF`HKQVK7&ngN?v8R~0dm z6d5pwI?>j4@)9z2m}~*@W(BfW_mb>2W@fT|ShgkmvhVx-;yuaDO;7LXx%aXyxli)T zxjpB3{_p?)IS>S8K}AqsA8>69wp0eevLFZ=8$bNJWpWT4;a+QN`uV&dxG*gUTG+!8 zPJDQuOEFg&!pFmOm>I$4+`kM)TAuBKZ^LmoF#<1h?-r**X}P#nt?M8vYK-(2h)BJg?2_^{nwI&%jh{hcloZ`eHo7m3dzMbK%-F z3%NGIRL~B6QI}`J6`o(sjTRV$cOV64L4EG20&R>s?=vbYSPCn^Qfr1KFbis7wDanp z4==(S@C<1C*Wo$vnR`alKauAPK^r`4ZOn!R;Ik#GF|8lerG zPo?#L5rImqoB$tzYrtN!*O$k{z1M^N+yKtPvDBlwohuul7wnq>7>da&>^ltG;YM)2 zYD3(m-|F58{csB21ZP$pd8m!x zje~V=t(j|MuHWj7yRKv7K`{P*18p!4=E8cgCL17X<*UCL!CwGl@GDTaxzL`IupR8p zs5P4Yat>8MC0Ngyc+Y3%*mt2e7zgbzm)4|xVQ*d!Q7cNnx~rfDoDtczP|LmX;CFyF z7zcA1TppK}O zt>1Yt18#9t@SZb@IAP0e6ET7Q0<>$~;kUe9QYGV;y8GIzsC zcpdzPbWT4C+MsUdr9Jm5xYsk*jy068{*NNV+ByZ+z?-0+FM<9w!RO&H90m7!W(MS= zKaBr$XvuIl!gRDgTD@6~%_)o;xVfcdqa#`^wMUnai0m7y$UDq9^< z%Nc83J4?O>o51tNAqk#UhV%avDBF8cYatJMK|A+@HfX#3lLXJITUq_k3-&|Q^4--1 zyFeR^gK>K!A)eK5b?<}?ppK~JEHlp9&T~ z36l`YP@dm;)!=uP`qZ26QieABt>`zU-@3l9RznA@hfdg#5YPH8+zxHvxAIk>U*o{< z#e9}=Fs@gEBTRi;VK?-F-#q?%08R@oWl|<#(8V+y{$cCYX0^Na};NCMU$X zb`N|Dehu3Gk2JsIst@KNX)S5Ladu7)gZ=ghSTn|b8ic)7!SAtB&l%+#pP#~?;h*qt z&=2!qE!htb!eR(#YdODjt%+<1?RQ3Me-BvebD#mVIq9n9pt1N0*uy^uZPo|vHy`$c zJ-r8EFV;a30LVW6%cXF4r21{nle;8gpx3MB#JyYQH_a95#V@u$K0L zz8nE(hCaOmzNgJWJ4}FFD~|ozq3zmK1=d6j7@sKIqwQWBKp)J5wPZhZfODxEoIU#E zdpec=y^d=g|_3EFdY1oqx6xB{lb6wnrBgnm1hl(7ibfIc`ohT$M| zLcaDlA>(rJ`(zFnw|n7!=!VUsU=Mx@jPVL+1oL4YT@_$FW`nctA?Sy5a0oVlvCXwc zi~oEM8$)S>FTxQx4kzKeV9vAg{t2+Io$J0&^uai2w?0^t#k602EnqDeBYW=v`~?00 z@4=to{Sol~AHmsj9;|KSITh@oOJTn`od?F=_u;M31I~;AuvWhV#yFcH?!5{xgSGrQ zxCzX)@h^j7$6x!K!1`COc~}Pa)m>md?gr~S8}Ga4PH>Lg2(zFHwB2VGbJrhs~D z!Tf87zGUJY@xHUkJ=V47oIU2w7&L%!aQ;|B;=MUgto|J-{_D}V5sa-d?gC@H3KoMl zJ7@Ic8dw3V!9B7ZoV)rm4b)LA`{yG&tbcWEg>S%%@KPH4;aTW~`#>M+VF6fUJ75pE zN1T)Pl6g_KJys0+jaxW>8u@((?1i)NGx$ZC3tZ2@QP5V;8CPpwTlLlX>Ymfk2j;C2 ztUGmh#=ZS;4h}&f=D)EC<8REIKhHrpe+Kz&9@MuRJ`GDCjQ>6S-Ua%7FwG-e+raos zvEpw|n;@J&_VR7;N$7^n@EOqNPe3Oa^O-On?B!NyhmCM=nl`S=y%hEDY;Z<6f9k+^ zsoR{}n+w4n(YGtXo~?mO&${hJNux)}8z+OI9;pk8e>w#Hf8{O!GQpiS;`g+6%3XZqqk z|5lz9!hU1Z0sCPX2EiU%3C&=P)vrG7Q-2gb4{ZzgXP*i8DARgVhq`)T0JOng?*whj zwcOJVeUO5quoJq#ery7LuvQC+GUvilP?xiJ2vT4y*Mqj@TJBi`d%@X$0(L_;tOI>8 zM%GdxQKq(=`)+swj=|f|2i}ikzx}idJbwy~!#>ys-OvT**B&b*%Jd!e0H{lw&Am1l zztx~^xt9HB9JYXU_dI+P^g%h+lKs$AV3eusCqZ3@;4mD3t*{KVE!T1`m`m&JVR#Dk z!8}+?#$>Qayq4Bs-Mj$mHcs~Le9*RBtC9`AE0=)h^uausNBbcK!*H&^C^H4>P?zyn zw{dI*ZPK<}tDFrJ!S|;=c-~sFANGQE={zU~%2vnYpiXsbgSJcpZOgTM&zL8D(C2V2 z*wbsE9Xg7{+O!UJEd_N?2W`ue*kAs3QYh2 diff --git a/internal/repositories/database/database.go b/internal/repositories/database/database.go index e4659ec..58b6ec2 100644 --- a/internal/repositories/database/database.go +++ b/internal/repositories/database/database.go @@ -1,78 +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) VALUES (?, ?, ?)` - _, err := db.conn.Exec(query, task.Id, task.Title, task.Description) + 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) ListTasks() ([]models.Task, error) { query := `SELECT id, title, description FROM tasks` - rows, err := db.conn.Query(query) - if err != nil { - return nil, err - } - - var tasks []models.Task - for rows.Next() { - var task models.Task - err := rows.Scan(&task.Id, &task.Title, &task.Description) - if err != nil { - return nil, err - } + q := db.app.DB().NewQuery(query) - 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, 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.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) + query := `DELETE FROM tasks WHERE id = {:Id}` + q := db.app.DB().NewQuery(query).Bind(dbx.Params{ + "Id": taskId, + }) + + _, err := q.Execute() return err } 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) + 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 } diff --git a/internal/routes/bind.go b/internal/routes/bind.go deleted file mode 100644 index 2e421e5..0000000 --- a/internal/routes/bind.go +++ /dev/null @@ -1,15 +0,0 @@ -package routes - -import ( - "goth/internal/controllers" - - "github.com/gofiber/fiber/v2" -) - -const cookieName = "goth:jwt" - -func controllerBind(controller *controllers.TaskController, handler func(*controllers.TaskController, *fiber.Ctx) error) fiber.Handler { - return func(ctx *fiber.Ctx) error { - return handler(controller, ctx) - } -} 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 f6ced97..0000000 --- a/internal/routes/init.go +++ /dev/null @@ -1,34 +0,0 @@ -package routes - -import ( - "fmt" - "goth/internal/config" - "goth/internal/controllers" - "goth/internal/repositories/database" - - "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 { - dbRepo, err := database.NewDatabaseRepo(cfg) - if err != nil { - return fmt.Errorf("[Init] failed to get database: %w", err) - } - - tc := controllers.NewTaskController(dbRepo) - - app.Get("/", controllerBind(tc, taskList)) - app.Get("/new", controllerBind(tc, taskNew)) - app.Get("/edit/:id", controllerBind(tc, taskEdit)) - app.Post("/edit/:id", controllerBind(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 f6ce150..5bc8ec8 100644 --- a/internal/routes/tasks.go +++ b/internal/routes/tasks.go @@ -1,52 +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) error { - tasks, err := tc.ListTasks() +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) error { - task, err := tc.CreateTask() - 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) error { - taskId := c.Params("id") - task, err := tc.RetrieveTask(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) 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(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/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() } From ffc368a2b8d6cb4697ab6e67d5245848d26caae9 Mon Sep 17 00:00:00 2001 From: felipereyel Date: Sun, 27 Apr 2025 17:15:01 -0300 Subject: [PATCH 4/5] migrations --- .env.example | 9 +- README.md | 13 +- internal/cmd/root.go | 13 +- internal/config/config.go | 46 +- .../1745782908_collections_snapshot.go | 864 ++++++++++++++++++ migrations/.gitignore | 1 + 6 files changed, 882 insertions(+), 64 deletions(-) create mode 100644 internal/migrations/1745782908_collections_snapshot.go create mode 100644 migrations/.gitignore diff --git a/.env.example b/.env.example index 908fcab..190a694 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1 @@ -# Optional - with default values - -PORT=3000 -AUTO_MIGRATE=true - -# Mandatory - -DATABASE_URL=db.sqlite +ENV=prod \ No newline at end of file diff --git a/README.md b/README.md index a64f362..7c98d88 100644 --- a/README.md +++ b/README.md @@ -28,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/internal/cmd/root.go b/internal/cmd/root.go index 729618f..f7d05f8 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -1,20 +1,25 @@ package cmd import ( + "goth/internal/config" "goth/internal/routes" "log" "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/plugins/migratecmd" + + _ "goth/internal/migrations" // register all migrations ) func Root() { app := pocketbase.New() + cfg := config.GetServerConfigs() app.OnServe().BindFunc(routes.SetupRoutes) + app.Settings().Meta.HideControls = cfg.IsProd - // cfg := config.GetServerConfigs() - // if cfg.AutoMigrate { - // migrations.Register() - // } + if !cfg.IsProd { + migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{}) + } if err := app.Start(); err != nil { log.Fatal(err) diff --git a/internal/config/config.go b/internal/config/config.go index 2ecd3af..e380626 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,53 +7,15 @@ import ( ) type ServerConfigs struct { - AutoMigrate bool - ServerAddress string - - DataBaseURL string + IsProd bool } func GetServerConfigs() ServerConfigs { config := ServerConfigs{} - // mandatory - - config.DataBaseURL = os.Getenv("DATABASE_URL") - if config.DataBaseURL == "" { - panic("Missing DATABASE_URL") - } - - // 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/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/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 From e0f2af3ef00e0a6f0504e5e8f138cdfedd792697 Mon Sep 17 00:00:00 2001 From: felipereyel Date: Sun, 27 Apr 2025 23:43:14 -0300 Subject: [PATCH 5/5] dockerfile --- Dockerfile | 4 ++-- go.mod | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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/go.mod b/go.mod index c9481f6..b47c80f 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module goth -go 1.23.0 +go 1.24 toolchain go1.24.1