From 9546ae2d68ff564debef5ba2fc08ff0042ea551f Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 15:49:08 +0700 Subject: [PATCH 1/7] feat: update models and use context to store username and name in middleware --- internal/middleware/auth.go | 13 +++++++++++-- internal/model/task.go | 2 ++ internal/model/user.go | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 6bf5d5c..26f9d87 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -1,6 +1,7 @@ package middleware import ( + "context" "devtasker/internal/utils" "strings" "time" @@ -8,6 +9,13 @@ import ( "github.com/gofiber/fiber/v2" ) +type ContextKey string + +const ( + UsernameKey ContextKey = "username" + NameKey ContextKey = "name" +) + func Authorization(c *fiber.Ctx) error { if strings.Contains(c.Path(), "auth") || strings.Contains(c.Path(), "doc") || @@ -39,8 +47,9 @@ func Authorization(c *fiber.Ctx) error { } } - c.Locals("username", claims["username"]) - c.Locals("name", claims["name"]) + ctx := context.WithValue(c.Context(), UsernameKey, claims["username"]) + ctx = context.WithValue(ctx, NameKey, claims["name"]) + c.SetUserContext(ctx) return c.Next() } diff --git a/internal/model/task.go b/internal/model/task.go index 187df31..ccea94e 100644 --- a/internal/model/task.go +++ b/internal/model/task.go @@ -19,4 +19,6 @@ type Task struct { Status TaskStatus `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + + UserUsername string } diff --git a/internal/model/user.go b/internal/model/user.go index d32f7e8..25b96f3 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -9,4 +9,6 @@ type User struct { PasswordHash string `json:"password"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` + + Tasks []Task `gorm:"foreignKey:UserUsername;references:Username"` } From 7e41acc72ed088ba070f72366a4c87f085e7e38b Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:02:13 +0700 Subject: [PATCH 2/7] feat: update create task with username --- internal/handler/task.go | 3 ++- internal/middleware/auth.go | 11 ++--------- internal/repository/task.go | 11 ++++++----- internal/service/task.go | 9 ++++++--- internal/utils/constant.go | 8 ++++++++ 5 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 internal/utils/constant.go diff --git a/internal/handler/task.go b/internal/handler/task.go index 64def88..798832a 100644 --- a/internal/handler/task.go +++ b/internal/handler/task.go @@ -41,12 +41,13 @@ func New(s service.ITaskService) *TaskHandler { // @Failure 500 {object} error // @Router /api/task [post] func (th *TaskHandler) CreateTask(c *fiber.Ctx) error { + ctx := c.UserContext() ctr := new(dto.CreateTaskRequest) if err := c.BodyParser(ctr); err != nil { utils.ErrorLogger.Println("Failed to parse the body:\n", c.Body()) return err } - t, err := th.s.CreateTask(ctr.Title, ctr.Description) + t, err := th.s.CreateTask(ctx, ctr.Title, ctr.Description) if err != nil { utils.ErrorLogger.Println("Failed to create a new task:\n", err) return c.Status(fiber.StatusInternalServerError).JSON(err) diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 26f9d87..c7eb03f 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -9,13 +9,6 @@ import ( "github.com/gofiber/fiber/v2" ) -type ContextKey string - -const ( - UsernameKey ContextKey = "username" - NameKey ContextKey = "name" -) - func Authorization(c *fiber.Ctx) error { if strings.Contains(c.Path(), "auth") || strings.Contains(c.Path(), "doc") || @@ -47,8 +40,8 @@ func Authorization(c *fiber.Ctx) error { } } - ctx := context.WithValue(c.Context(), UsernameKey, claims["username"]) - ctx = context.WithValue(ctx, NameKey, claims["name"]) + ctx := context.WithValue(c.Context(), utils.UsernameKey, claims["username"]) + ctx = context.WithValue(ctx, utils.NameKey, claims["name"]) c.SetUserContext(ctx) return c.Next() diff --git a/internal/repository/task.go b/internal/repository/task.go index 493bd55..5e881e4 100644 --- a/internal/repository/task.go +++ b/internal/repository/task.go @@ -7,7 +7,7 @@ import ( ) type ITaskRepository interface { - CreateTask(title, description string) (model.Task, error) + CreateTask(username, title, description string) (model.Task, error) GetTaskByID(id string) (model.Task, error) GetAllTasks() ([]model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) @@ -24,11 +24,12 @@ func New(db *gorm.DB) *TaskRepository { } } -func (tr *TaskRepository) CreateTask(title, description string) (model.Task, error) { +func (tr *TaskRepository) CreateTask(username, title, description string) (model.Task, error) { t := model.Task{ - Title: title, - Description: description, - Status: model.Pending, + Title: title, + Description: description, + Status: model.Pending, + UserUsername: username, } tr.db.Create(&t) return t, nil diff --git a/internal/service/task.go b/internal/service/task.go index 9bf27a7..50e84ae 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -1,13 +1,15 @@ package service import ( + "context" "devtasker/internal/model" "devtasker/internal/repository" + "devtasker/internal/utils" "fmt" ) type ITaskService interface { - CreateTask(title, description string) (model.Task, error) + CreateTask(ctx context.Context, title, description string) (model.Task, error) GetTaskByID(id string) (model.Task, error) GetAllTasks() ([]model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) @@ -24,11 +26,12 @@ func New(r repository.ITaskRepository) *TaskService { } } -func (ts *TaskService) CreateTask(title, description string) (model.Task, error) { +func (ts *TaskService) CreateTask(ctx context.Context, title, description string) (model.Task, error) { if title == "" || description == "" { return model.Task{}, fmt.Errorf("title and description cannot be empty") } - t, err := ts.r.CreateTask(title, description) + username, _ := ctx.Value(utils.UsernameKey).(string) + t, err := ts.r.CreateTask(username, title, description) if err != nil { return model.Task{}, err } diff --git a/internal/utils/constant.go b/internal/utils/constant.go new file mode 100644 index 0000000..4277aa0 --- /dev/null +++ b/internal/utils/constant.go @@ -0,0 +1,8 @@ +package utils + +type ContextKey string + +const ( + UsernameKey ContextKey = "username" + NameKey ContextKey = "name" +) From 2596106a9a5611408a501a2a94664d85fda557c3 Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:05:36 +0700 Subject: [PATCH 3/7] feat: update get all tasks --- internal/handler/task.go | 3 ++- internal/repository/task.go | 6 +++--- internal/service/task.go | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/handler/task.go b/internal/handler/task.go index 798832a..4133343 100644 --- a/internal/handler/task.go +++ b/internal/handler/task.go @@ -64,7 +64,8 @@ func (th *TaskHandler) CreateTask(c *fiber.Ctx) error { // @Failure 500 {object} error // @Router /api/task [get] func (th *TaskHandler) GetAllTasks(c *fiber.Ctx) error { - tasks, err := th.s.GetAllTasks() + ctx := c.UserContext() + tasks, err := th.s.GetAllTasks(ctx) if err != nil { utils.ErrorLogger.Println("Failed to get all tasks:\n", err) return c.Status(fiber.StatusInternalServerError).JSON(err) diff --git a/internal/repository/task.go b/internal/repository/task.go index 5e881e4..68fb040 100644 --- a/internal/repository/task.go +++ b/internal/repository/task.go @@ -9,7 +9,7 @@ import ( type ITaskRepository interface { CreateTask(username, title, description string) (model.Task, error) GetTaskByID(id string) (model.Task, error) - GetAllTasks() ([]model.Task, error) + GetAllTasks(username string) ([]model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(id string) (model.Task, error) } @@ -44,9 +44,9 @@ func (tr *TaskRepository) GetTaskByID(id string) (model.Task, error) { return task, nil } -func (tr *TaskRepository) GetAllTasks() ([]model.Task, error) { +func (tr *TaskRepository) GetAllTasks(username string) ([]model.Task, error) { var tasks []model.Task - result := tr.db.Find(&tasks) + result := tr.db.Where("user_username = ?", username).Find(&tasks) if result.Error != nil { return []model.Task{}, result.Error } diff --git a/internal/service/task.go b/internal/service/task.go index 50e84ae..c4d0d92 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -11,7 +11,7 @@ import ( type ITaskService interface { CreateTask(ctx context.Context, title, description string) (model.Task, error) GetTaskByID(id string) (model.Task, error) - GetAllTasks() ([]model.Task, error) + GetAllTasks(ctx context.Context) ([]model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(id string) (model.Task, error) } @@ -46,8 +46,9 @@ func (ts *TaskService) GetTaskByID(id string) (model.Task, error) { return t, nil } -func (ts *TaskService) GetAllTasks() ([]model.Task, error) { - tasks, err := ts.r.GetAllTasks() +func (ts *TaskService) GetAllTasks(ctx context.Context) ([]model.Task, error) { + username, _ := ctx.Value(utils.UsernameKey).(string) + tasks, err := ts.r.GetAllTasks(username) if err != nil { return []model.Task{}, nil } From d61d72f364c0921f8086305356367d3040706096 Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:07:25 +0700 Subject: [PATCH 4/7] feat: update get task by id --- internal/handler/task.go | 3 ++- internal/service/task.go | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/handler/task.go b/internal/handler/task.go index 4133343..3438b81 100644 --- a/internal/handler/task.go +++ b/internal/handler/task.go @@ -83,8 +83,9 @@ func (th *TaskHandler) GetAllTasks(c *fiber.Ctx) error { // @Failure 404 {object} error // @Router /api/task/{id} [get] func (th *TaskHandler) GetTaskByID(c *fiber.Ctx) error { + ctx := c.UserContext() id := c.Params("id") - t, err := th.s.GetTaskByID(id) + t, err := th.s.GetTaskByID(ctx, id) if err != nil { utils.ErrorLogger.Printf("Failed to get task with id %s:\n%s", id, err) return c.Status(fiber.StatusNotFound).JSON(err) diff --git a/internal/service/task.go b/internal/service/task.go index c4d0d92..61456f4 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -10,7 +10,7 @@ import ( type ITaskService interface { CreateTask(ctx context.Context, title, description string) (model.Task, error) - GetTaskByID(id string) (model.Task, error) + GetTaskByID(ctx context.Context, id string) (model.Task, error) GetAllTasks(ctx context.Context) ([]model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(id string) (model.Task, error) @@ -38,11 +38,15 @@ func (ts *TaskService) CreateTask(ctx context.Context, title, description string return t, nil } -func (ts *TaskService) GetTaskByID(id string) (model.Task, error) { +func (ts *TaskService) GetTaskByID(ctx context.Context, id string) (model.Task, error) { t, err := ts.r.GetTaskByID(id) if err != nil { return model.Task{}, err } + username, _ := ctx.Value(utils.UsernameKey).(string) + if t.UserUsername != username { + return model.Task{}, fmt.Errorf("you don't have permission to access this task") + } return t, nil } From 94dd72eabaca9280fd64913de2fac37ce81c6e6d Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:11:34 +0700 Subject: [PATCH 5/7] feat: adjust update task --- internal/handler/task.go | 2 ++ internal/service/task.go | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/handler/task.go b/internal/handler/task.go index 3438b81..a732f7d 100644 --- a/internal/handler/task.go +++ b/internal/handler/task.go @@ -105,6 +105,7 @@ func (th *TaskHandler) GetTaskByID(c *fiber.Ctx) error { // @Failure 500 {object} error // @Router /api/task/{id} [patch] func (th *TaskHandler) UpdateTask(c *fiber.Ctx) error { + ctx := c.UserContext() id := c.Params("id") b := new(dto.UpdateTaskRequest) if err := c.BodyParser(b); err != nil { @@ -112,6 +113,7 @@ func (th *TaskHandler) UpdateTask(c *fiber.Ctx) error { return err } t, err := th.s.UpdateTask( + ctx, id, b.Title, b.Description, diff --git a/internal/service/task.go b/internal/service/task.go index 61456f4..6278c20 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -12,7 +12,7 @@ type ITaskService interface { CreateTask(ctx context.Context, title, description string) (model.Task, error) GetTaskByID(ctx context.Context, id string) (model.Task, error) GetAllTasks(ctx context.Context) ([]model.Task, error) - UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) + UpdateTask(ctx context.Context, id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(id string) (model.Task, error) } @@ -59,10 +59,24 @@ func (ts *TaskService) GetAllTasks(ctx context.Context) ([]model.Task, error) { return tasks, nil } -func (ts *TaskService) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) { +func (ts *TaskService) UpdateTask(ctx context.Context, id, title, description string, status model.TaskStatus) (model.Task, error) { if title == "" || description == "" || status == "" { return model.Task{}, fmt.Errorf("title and description cannot be empty") } + + // Step 1: Get task by ID + task, err := ts.r.GetTaskByID(id) + if err != nil { + return model.Task{}, fmt.Errorf("task not found") + } + + // Step 2: Check ownership + username, _ := ctx.Value(utils.UsernameKey).(string) + if task.UserUsername != username { + return model.Task{}, fmt.Errorf("you don't have permission to access this task") + } + + // Step 3: Update the task t, err := ts.r.UpdateTask(id, title, description, status) if err != nil { return model.Task{}, err From 5b7b4e0cc737078850ae2d9f62adf8b4b875359f Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:13:54 +0700 Subject: [PATCH 6/7] feat update delete task --- internal/handler/task.go | 3 ++- internal/service/task.go | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/internal/handler/task.go b/internal/handler/task.go index a732f7d..25fbc6d 100644 --- a/internal/handler/task.go +++ b/internal/handler/task.go @@ -136,8 +136,9 @@ func (th *TaskHandler) UpdateTask(c *fiber.Ctx) error { // @Failure 500 {object} error // @Router /api/task/{id} [delete] func (th *TaskHandler) DeleteTask(c *fiber.Ctx) error { + ctx := c.UserContext() id := c.Params("id") - t, err := th.s.DeleteTask(id) + t, err := th.s.DeleteTask(ctx, id) if err != nil { utils.ErrorLogger.Printf("Failed to delete task with id %s:\n%s", id, err) return c.Status(fiber.StatusInternalServerError).JSON(err) diff --git a/internal/service/task.go b/internal/service/task.go index 6278c20..a1c56f9 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -13,7 +13,7 @@ type ITaskService interface { GetTaskByID(ctx context.Context, id string) (model.Task, error) GetAllTasks(ctx context.Context) ([]model.Task, error) UpdateTask(ctx context.Context, id, title, description string, status model.TaskStatus) (model.Task, error) - DeleteTask(id string) (model.Task, error) + DeleteTask(ctx context.Context, id string) (model.Task, error) } type TaskService struct { @@ -63,19 +63,16 @@ func (ts *TaskService) UpdateTask(ctx context.Context, id, title, description st if title == "" || description == "" || status == "" { return model.Task{}, fmt.Errorf("title and description cannot be empty") } - // Step 1: Get task by ID task, err := ts.r.GetTaskByID(id) if err != nil { return model.Task{}, fmt.Errorf("task not found") } - // Step 2: Check ownership username, _ := ctx.Value(utils.UsernameKey).(string) if task.UserUsername != username { return model.Task{}, fmt.Errorf("you don't have permission to access this task") } - // Step 3: Update the task t, err := ts.r.UpdateTask(id, title, description, status) if err != nil { @@ -84,7 +81,18 @@ func (ts *TaskService) UpdateTask(ctx context.Context, id, title, description st return t, nil } -func (ts *TaskService) DeleteTask(id string) (model.Task, error) { +func (ts *TaskService) DeleteTask(ctx context.Context, id string) (model.Task, error) { + // Step 1: Get task by ID + task, err := ts.r.GetTaskByID(id) + if err != nil { + return model.Task{}, fmt.Errorf("task not found") + } + // Step 2: Check ownership + username, _ := ctx.Value(utils.UsernameKey).(string) + if task.UserUsername != username { + return model.Task{}, fmt.Errorf("you don't have permission to access this task") + } + // Step 3: Delete the task t, err := ts.r.DeleteTask(id) if err != nil { return model.Task{}, err From 13d1a1eb336b71fd14087be355b870589c53a4b7 Mon Sep 17 00:00:00 2001 From: iqbalpa Date: Mon, 14 Jul 2025 16:15:19 +0700 Subject: [PATCH 7/7] chore: reorder func --- internal/repository/task.go | 32 ++++++++++++++++---------------- internal/service/task.go | 26 +++++++++++++------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/internal/repository/task.go b/internal/repository/task.go index 68fb040..c65e9e1 100644 --- a/internal/repository/task.go +++ b/internal/repository/task.go @@ -7,9 +7,9 @@ import ( ) type ITaskRepository interface { - CreateTask(username, title, description string) (model.Task, error) - GetTaskByID(id string) (model.Task, error) GetAllTasks(username string) ([]model.Task, error) + GetTaskByID(id string) (model.Task, error) + CreateTask(username, title, description string) (model.Task, error) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(id string) (model.Task, error) } @@ -24,15 +24,13 @@ func New(db *gorm.DB) *TaskRepository { } } -func (tr *TaskRepository) CreateTask(username, title, description string) (model.Task, error) { - t := model.Task{ - Title: title, - Description: description, - Status: model.Pending, - UserUsername: username, +func (tr *TaskRepository) GetAllTasks(username string) ([]model.Task, error) { + var tasks []model.Task + result := tr.db.Where("user_username = ?", username).Find(&tasks) + if result.Error != nil { + return []model.Task{}, result.Error } - tr.db.Create(&t) - return t, nil + return tasks, nil } func (tr *TaskRepository) GetTaskByID(id string) (model.Task, error) { @@ -44,13 +42,15 @@ func (tr *TaskRepository) GetTaskByID(id string) (model.Task, error) { return task, nil } -func (tr *TaskRepository) GetAllTasks(username string) ([]model.Task, error) { - var tasks []model.Task - result := tr.db.Where("user_username = ?", username).Find(&tasks) - if result.Error != nil { - return []model.Task{}, result.Error +func (tr *TaskRepository) CreateTask(username, title, description string) (model.Task, error) { + t := model.Task{ + Title: title, + Description: description, + Status: model.Pending, + UserUsername: username, } - return tasks, nil + tr.db.Create(&t) + return t, nil } func (tr *TaskRepository) UpdateTask(id, title, description string, status model.TaskStatus) (model.Task, error) { diff --git a/internal/service/task.go b/internal/service/task.go index a1c56f9..6be8e6f 100644 --- a/internal/service/task.go +++ b/internal/service/task.go @@ -9,9 +9,9 @@ import ( ) type ITaskService interface { - CreateTask(ctx context.Context, title, description string) (model.Task, error) - GetTaskByID(ctx context.Context, id string) (model.Task, error) GetAllTasks(ctx context.Context) ([]model.Task, error) + GetTaskByID(ctx context.Context, id string) (model.Task, error) + CreateTask(ctx context.Context, title, description string) (model.Task, error) UpdateTask(ctx context.Context, id, title, description string, status model.TaskStatus) (model.Task, error) DeleteTask(ctx context.Context, id string) (model.Task, error) } @@ -26,16 +26,13 @@ func New(r repository.ITaskRepository) *TaskService { } } -func (ts *TaskService) CreateTask(ctx context.Context, title, description string) (model.Task, error) { - if title == "" || description == "" { - return model.Task{}, fmt.Errorf("title and description cannot be empty") - } +func (ts *TaskService) GetAllTasks(ctx context.Context) ([]model.Task, error) { username, _ := ctx.Value(utils.UsernameKey).(string) - t, err := ts.r.CreateTask(username, title, description) + tasks, err := ts.r.GetAllTasks(username) if err != nil { - return model.Task{}, err + return []model.Task{}, nil } - return t, nil + return tasks, nil } func (ts *TaskService) GetTaskByID(ctx context.Context, id string) (model.Task, error) { @@ -50,13 +47,16 @@ func (ts *TaskService) GetTaskByID(ctx context.Context, id string) (model.Task, return t, nil } -func (ts *TaskService) GetAllTasks(ctx context.Context) ([]model.Task, error) { +func (ts *TaskService) CreateTask(ctx context.Context, title, description string) (model.Task, error) { + if title == "" || description == "" { + return model.Task{}, fmt.Errorf("title and description cannot be empty") + } username, _ := ctx.Value(utils.UsernameKey).(string) - tasks, err := ts.r.GetAllTasks(username) + t, err := ts.r.CreateTask(username, title, description) if err != nil { - return []model.Task{}, nil + return model.Task{}, err } - return tasks, nil + return t, nil } func (ts *TaskService) UpdateTask(ctx context.Context, id, title, description string, status model.TaskStatus) (model.Task, error) {