Skip to content

Commit

Permalink
add log errors, refactor models
Browse files Browse the repository at this point in the history
  • Loading branch information
alexferl committed Jan 17, 2024
1 parent 3b58ea1 commit fcac837
Show file tree
Hide file tree
Showing 32 changed files with 472 additions and 288 deletions.
2 changes: 1 addition & 1 deletion configs/config.local.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ env-name = "local"
log-level = "DEBUG"

cookies-enabled = true
csrf-enabled = true
csrf-enabled = false
csrf-secret-key = "changeme"

http-cors-enabled = true
Expand Down
28 changes: 17 additions & 11 deletions handlers/tasks/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package tasks
import (
"context"
"errors"
"fmt"
"net/http"

"github.com/alexferl/echo-openapi"
"github.com/alexferl/golib/http/api/server"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/rs/zerolog"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
Expand Down Expand Up @@ -46,22 +46,25 @@ func (h *Handler) AddRoutes(s *server.Server) {
}

func (h *Handler) getTask(ctx context.Context, c echo.Context, taskId string, token jwt.Token) (*Task, func() error) {
logger := c.Get("logger").(zerolog.Logger)

result, err := h.Mapper.FindOneById(ctx, taskId, &Task{})
if err != nil {
if errors.Is(err, data.ErrNoDocuments) {
return nil, util.Wrap(h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"}))
return nil, util.WrapErr(h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"}))
}
return nil, util.Wrap(fmt.Errorf("failed getting task: %v", err))
logger.Error().Err(err).Msg("failed getting task")
return nil, util.WrapErr(err)
}

task := result.(*Task)
if task.DeletedAt != nil {
return nil, util.Wrap(h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"}))
return nil, util.WrapErr(h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"}))
}

if token != nil {
if token.Subject() != task.CreatedBy && !util.HasRole(token, users.AdminRole.String()) {
return nil, util.Wrap(h.Validate(c, http.StatusForbidden, echo.Map{"message": "you don't have access"}))
return nil, util.WrapErr(h.Validate(c, http.StatusForbidden, echo.Map{"message": "you don't have access"}))
}
}

Expand Down Expand Up @@ -112,21 +115,24 @@ func (h *Handler) getPipeline(filter any, limit int, skip int) mongo.Pipeline {
}

func (h *Handler) getAggregate(ctx context.Context, c echo.Context) (*TaskResponse, func() error) {
logger := c.Get("logger").(zerolog.Logger)

pipeline := h.getPipeline(bson.D{{"id", c.Param("id")}}, 1, 0)
result, err := h.Mapper.Aggregate(ctx, pipeline, []*TaskResponse{})
result, err := h.Mapper.Aggregate(ctx, pipeline, TasksAggregate{})
if err != nil {
return nil, util.Wrap(fmt.Errorf("failed getting task: %v", err))
logger.Error().Err(err).Msg("failed getting task")
return nil, util.WrapErr(err)
}

tasks := result.([]*TaskResponse)
tasks := result.(TasksAggregate)
if len(tasks) < 1 {
return nil, util.Wrap(h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"}))
return nil, util.WrapErr(h.Validate(c, http.StatusNotFound, echo.Map{"message": "task not found"}))
}

task := tasks[0]
if task.DeletedAt != nil {
return nil, util.Wrap(h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"}))
return nil, util.WrapErr(h.Validate(c, http.StatusGone, echo.Map{"message": "task was deleted"}))
}

return task, nil
return task.Response(), nil
}
108 changes: 74 additions & 34 deletions handlers/tasks/model.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,84 @@
package tasks

import (
"fmt"
"time"

"github.com/alexferl/echo-boilerplate/data"
"github.com/alexferl/echo-boilerplate/handlers/users"
"github.com/alexferl/echo-boilerplate/util"
)

type Task struct {
*data.Model `bson:",inline"`
Title string `json:"title" bson:"title"`
Completed bool `json:"completed" bson:"completed"`
CompletedAt *time.Time `json:"completed_at" bson:"completed_at"`
CompletedBy string `json:"completed_by" bson:"completed_by"`
Title string `json:"title" bson:"title"`
}

type TaskAggregate struct {
*data.Model `bson:",inline"`
CreatedAt *time.Time `json:"created_at" bson:"created_at"`
CreatedBy *users.User `json:"created_by" bson:"created_by"`
DeletedAt *time.Time `json:"-" bson:"deleted_at"`
DeletedBy string `json:"-" bson:"deleted_by"`
UpdatedAt *time.Time `json:"updated_at" bson:"updated_at"`
UpdatedBy *users.User `json:"updated_by" bson:"updated_by"`
Completed bool `json:"completed" bson:"completed"`
CompletedAt *time.Time `json:"completed_at" bson:"completed_at"`
CompletedBy *users.User `json:"completed_by" bson:"completed_by"`
Title string `json:"title" bson:"title"`
}

func (ta *TaskAggregate) Response() *TaskResponse {
resp := &TaskResponse{
Id: ta.Id,
Href: util.GetFullURL(fmt.Sprintf("/tasks/%s", ta.Id)),
CreatedAt: ta.CreatedAt,
CreatedBy: ta.CreatedBy.Public(),
DeletedAt: ta.DeletedAt,
DeletedBy: ta.DeletedBy,
UpdatedAt: ta.UpdatedAt,
Title: ta.Title,
Completed: ta.Completed,
CompletedAt: ta.CompletedAt,
}

if ta.UpdatedBy != nil {
resp.UpdatedBy = ta.UpdatedBy.Public()
}

if ta.CompletedBy != nil {
resp.CompletedBy = ta.CompletedBy.Public()
}

return resp
}

type TasksAggregate []TaskAggregate

func (ta TasksAggregate) Response() []*TaskResponse {
res := make([]*TaskResponse, 0)
for _, task := range ta {
res = append(res, task.Response())
}
return res
}

type TaskResponse struct {
Id string `json:"id" bson:"id"`
Href string `json:"href" bson:"href"`
CreatedAt *time.Time `json:"created_at" bson:"created_at"`
CreatedBy *users.UserResponsePublic `json:"created_by" bson:"created_by"`
DeletedAt *time.Time `json:"-" bson:"deleted_at"`
DeletedBy string `json:"-" bson:"deleted_by"`
UpdatedAt *time.Time `json:"updated_at" bson:"updated_at"`
UpdatedBy *users.UserResponsePublic `json:"updated_by" bson:"updated_by"`
Completed bool `json:"completed" bson:"completed"`
CompletedAt *time.Time `json:"completed_at" bson:"completed_at"`
CompletedBy *users.UserResponsePublic `json:"completed_by" bson:"completed_by"`
Title string `json:"title" bson:"title"`
}

func NewTask(id string) *Task {
Expand All @@ -34,29 +100,11 @@ func (t *Task) Incomplete() {
t.CompletedBy = ""
}

type TaskResponse struct {
Id string `json:"id" bson:"id"`
CreatedAt *time.Time `json:"created_at" bson:"created_at"`
CreatedBy *users.PublicUser `json:"created_by" bson:"created_by"`
DeletedAt *time.Time `json:"-" bson:"deleted_at"`
DeletedBy string `json:"-" bson:"deleted_by"`
UpdatedAt *time.Time `json:"updated_at" bson:"updated_at"`
UpdatedBy *users.PublicUser `json:"updated_by" bson:"updated_by"`
Title string `json:"title" bson:"title"`
Completed bool `json:"completed" bson:"completed"`
CompletedAt *time.Time `json:"completed_at" bson:"completed_at"`
CompletedBy *users.PublicUser `json:"completed_by" bson:"completed_by"`
}

func (t *Task) MakeResponse(createdBy *users.User, updatedBy *users.User, completedBy *users.User) *TaskResponse {
resp := &TaskResponse{
Id: t.Id,
CreatedAt: t.CreatedAt,
CreatedBy: &users.PublicUser{
Id: createdBy.Id,
Username: createdBy.Username,
Name: createdBy.Name,
},
func (t *Task) Aggregate(createdBy *users.User, updatedBy *users.User, completedBy *users.User) *TaskAggregate {
resp := &TaskAggregate{
Model: t.Model,
CreatedAt: t.CreatedAt,
CreatedBy: createdBy,
DeletedAt: t.DeletedAt,
DeletedBy: t.DeletedBy,
UpdatedAt: t.UpdatedAt,
Expand All @@ -66,19 +114,11 @@ func (t *Task) MakeResponse(createdBy *users.User, updatedBy *users.User, comple
}

if updatedBy != nil {
resp.UpdatedBy = &users.PublicUser{
Id: updatedBy.Id,
Username: updatedBy.Username,
Name: updatedBy.Name,
}
resp.UpdatedBy = updatedBy
}

if completedBy != nil {
resp.CompletedBy = &users.PublicUser{
Id: completedBy.Id,
Username: completedBy.Username,
Name: completedBy.Name,
}
resp.CompletedBy = completedBy
}

return resp
Expand Down
29 changes: 20 additions & 9 deletions handlers/tasks/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/rs/zerolog"
"go.mongodb.org/mongo-driver/bson"
)

Expand All @@ -28,13 +29,16 @@ type UpdateTaskRequest struct {
}

func (h *Handler) UpdateTask(c echo.Context) error {
id := c.Param("id")
logger := c.Get("logger").(zerolog.Logger)
token := c.Get("token").(jwt.Token)

body := &UpdateTaskRequest{}
if err := c.Bind(body); err != nil {
logger.Error().Err(err).Msg("failed binding body")
return err
}

id := c.Param("id")
token := c.Get("token").(jwt.Token)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
task, errResp := h.getTask(ctx, c, id, token)
Expand All @@ -58,26 +62,32 @@ func (h *Handler) UpdateTask(c echo.Context) error {

_, err := h.Mapper.UpdateOneById(ctx, id, task)
if err != nil {
return fmt.Errorf("failed updating task: %v", err)
logger.Error().Err(err).Msg("failed updating task")
return err
}

pipeline := h.getPipeline(bson.D{{"id", id}}, 1, 0)
result, err := h.Mapper.Aggregate(ctx, pipeline, []*TaskResponse{})
result, err := h.Mapper.Aggregate(ctx, pipeline, TasksAggregate{})
if err != nil {
return fmt.Errorf("failed getting tasks: %v", err)
logger.Error().Err(err).Msg("failed getting task")
return err
}

res := result.([]*TaskResponse)
res := result.(TasksAggregate)
if len(res) < 1 {
return fmt.Errorf("failed to retrieve updated task: %v", err)
msg := "failed to retrieve updated task"
logger.Error().Msg(msg)
return fmt.Errorf(msg)
}

return h.Validate(c, http.StatusOK, res[0])
return h.Validate(c, http.StatusOK, res[0].Response())
}

func (h *Handler) DeleteTask(c echo.Context) error {
id := c.Param("id")
logger := c.Get("logger").(zerolog.Logger)
token := c.Get("token").(jwt.Token)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
task, errResp := h.getTask(ctx, c, id, token)
Expand All @@ -89,7 +99,8 @@ func (h *Handler) DeleteTask(c echo.Context) error {

_, err := h.Mapper.UpdateOneById(ctx, id, task, nil)
if err != nil {
return fmt.Errorf("failed deleting task: %v", err)
logger.Error().Err(err).Msg("failed deleting task")
return err
}

return h.Validate(c, http.StatusNoContent, nil)
Expand Down
14 changes: 7 additions & 7 deletions handlers/tasks/task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestHandler_GetTask_200(t *testing.T) {

newTask := tasks.NewTask("1")
newTask.Create(user.Id)
task := newTask.MakeResponse(user, nil, nil)
task := newTask.Aggregate(user, nil, nil)

req := httptest.NewRequest(http.MethodGet, "/tasks/id", nil)
req.Header.Set("Content-Type", "application/json")
Expand All @@ -42,7 +42,7 @@ func TestHandler_GetTask_200(t *testing.T) {
mock.Anything,
).
Return(
[]*tasks.TaskResponse{task},
tasks.TasksAggregate{*task},
nil,
)

Expand Down Expand Up @@ -85,7 +85,7 @@ func TestHandler_GetTask_404(t *testing.T) {
mock.Anything,
).
Return(
[]*tasks.TaskResponse{},
tasks.TasksAggregate{},
nil,
)

Expand All @@ -104,7 +104,7 @@ func TestHandler_GetTask_410(t *testing.T) {
newTask := tasks.NewTask("1")
newTask.Create(user.Id)
newTask.Delete(user.Id)
task := newTask.MakeResponse(user, nil, nil)
task := newTask.Aggregate(user, nil, nil)

req := httptest.NewRequest(http.MethodGet, "/tasks/id", nil)
req.Header.Set("Content-Type", "application/json")
Expand All @@ -121,7 +121,7 @@ func TestHandler_GetTask_410(t *testing.T) {
mock.Anything,
).
Return(
[]*tasks.TaskResponse{task},
tasks.TasksAggregate{*task},
nil,
)

Expand Down Expand Up @@ -150,7 +150,7 @@ func TestHandler_UpdateTask_200(t *testing.T) {
newTask.Title = payload.Title
newTask.Complete(user.Id)

updated := newTask.MakeResponse(user, nil, user)
updated := newTask.Aggregate(user, nil, user)

req := httptest.NewRequest(http.MethodPut, "/tasks/id", bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")
Expand Down Expand Up @@ -186,7 +186,7 @@ func TestHandler_UpdateTask_200(t *testing.T) {
mock.Anything,
).
Return(
[]*tasks.TaskResponse{updated},
tasks.TasksAggregate{*updated},
nil,
)

Expand Down
Loading

0 comments on commit fcac837

Please sign in to comment.