Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a81af77
feat: add sql migration for projects table
edwintantawi Jan 12, 2023
33db45c
feat: add project entity
edwintantawi Jan 12, 2023
50664b3
feat: add project repository
edwintantawi Jan 12, 2023
e4e3ec6
feat: add project create dto
edwintantawi Jan 12, 2023
f59cf65
feat: add project repository interface and mock
edwintantawi Jan 12, 2023
7907d52
feat: add project usecase create
edwintantawi Jan 12, 2023
c44b4cf
feat: add project usecase interface and mock
edwintantawi Jan 12, 2023
8b312e1
feat: add project post http handler
edwintantawi Jan 12, 2023
473e4b6
feat: error translator for ErrTitleEmpty
edwintantawi Jan 12, 2023
13e80ad
feat: add project repository find all by user id
edwintantawi Jan 13, 2023
fd390be
feat: get user_id column in repository task find all by user id
edwintantawi Jan 13, 2023
49ee169
feat: add project get all dto
edwintantawi Jan 13, 2023
75b990b
feat: add project repository find all by user id interface and mock
edwintantawi Jan 13, 2023
d5fdd45
feat: add project usecase get all
edwintantawi Jan 13, 2023
400249f
feat: add project usecase get all interface and mock
edwintantawi Jan 13, 2023
22fd786
feat: add project get http handler
edwintantawi Jan 13, 2023
fdf7f5c
feat: add project get http route
edwintantawi Jan 13, 2023
63e94f7
feat(test): add NewNullString and NewNullTime
edwintantawi Jan 14, 2023
d41f63f
feat: add NullString
edwintantawi Jan 14, 2023
d122cbd
feat: add project id
edwintantawi Jan 14, 2023
c5bb528
feat: use entity project id
edwintantawi Jan 14, 2023
1678964
feat: add project id in task create and update
edwintantawi Jan 14, 2023
502fcae
feat: add project not implemented get by id
edwintantawi Jan 14, 2023
118eda3
feat: add project repository find by id
edwintantawi Jan 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
authMiddleware "github.com/edwintantawi/taskit/internal/auth/delivery/http/middleware"
authRepository "github.com/edwintantawi/taskit/internal/auth/repository"
authUsecase "github.com/edwintantawi/taskit/internal/auth/usecase"
projectHTTPHandler "github.com/edwintantawi/taskit/internal/project/delivery/http"
projectRepository "github.com/edwintantawi/taskit/internal/project/repository"
projectUsecase "github.com/edwintantawi/taskit/internal/project/usecase"
taskHTTPHandler "github.com/edwintantawi/taskit/internal/task/delivery/http"
taskRepository "github.com/edwintantawi/taskit/internal/task/repository"
taskUsecase "github.com/edwintantawi/taskit/internal/task/usecase"
Expand Down Expand Up @@ -63,6 +66,11 @@ func main() {
taskUsecase := taskUsecase.New(&taskRepository)
taskHTTPHandler := taskHTTPHandler.New(&validator, &taskUsecase)

// Project.
projectRepository := projectRepository.New(db, &idProvider)
projectUsecase := projectUsecase.New(&projectRepository)
projectHTTPHandler := projectHTTPHandler.New(&validator, &projectUsecase)

// Create new router.
r := chi.NewRouter()
r.Use(middleware.Logger)
Expand Down Expand Up @@ -93,6 +101,9 @@ func main() {
r.Get("/api/tasks/{task_id}", taskHTTPHandler.GetByID)
r.Delete("/api/tasks/{task_id}", taskHTTPHandler.Delete)
r.Put("/api/tasks/{task_id}", taskHTTPHandler.Put)

r.Post("/api/projects", projectHTTPHandler.Post)
r.Get("/api/projects", projectHTTPHandler.Get)
})

// Start HTTP server.
Expand Down
11 changes: 5 additions & 6 deletions internal/domain/dto/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package dto
import "errors"

var (
ErrEmailEmpty = errors.New("dto.email_empty")
ErrPasswordEmpty = errors.New("dto.password_empty")
ErrNameEmpty = errors.New("dto.name_empty")

ErrEmailEmpty = errors.New("dto.email_empty")
ErrPasswordEmpty = errors.New("dto.password_empty")
ErrNameEmpty = errors.New("dto.name_empty")
ErrRefreshTokenEmpty = errors.New("dto.refresh_token_empty")

ErrContentEmpty = errors.New("dto.content_empty")
ErrContentEmpty = errors.New("dto.content_empty")
ErrTitleEmpty = errors.New("dto.title_empty")
)
52 changes: 52 additions & 0 deletions internal/domain/dto/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dto

import (
"time"

"github.com/edwintantawi/taskit/internal/domain/entity"
)

// ProjectCreateIn represent create project input.
type ProjectCreateIn struct {
UserID entity.UserID `json:"-"`
Title string `json:"title"`
}

func (d *ProjectCreateIn) Validate() error {
switch {
case d.Title == "":
return ErrTitleEmpty
}
return nil
}

// ProjectCreateIn represent create project input.
type ProjectCreateOut struct {
ID entity.ProjectID `json:"id"`
}

// ProjectGetAllIn represent get all project input.
type ProjectGetAllIn struct {
UserID entity.UserID `json:"-"`
}

// ProjectGetAllOut represent get all project output.
type ProjectGetAllOut struct {
ID entity.ProjectID `json:"id"`
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// ProjectGetByIDIn represent get by id project input.
type ProjectGetByIDIn struct {
ID entity.ProjectID `json:"-"`
}

// ProjectGetByIDOut represent get by id project output.
type ProjectGetByIDOut struct {
ID entity.ProjectID `json:"id"`
Title string `json:"title"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
43 changes: 43 additions & 0 deletions internal/domain/dto/project_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package dto

import (
"testing"

"github.com/stretchr/testify/suite"
)

type ProjectDTOTestSuite struct {
suite.Suite
}

func TestProjectDTOSuite(t *testing.T) {
suite.Run(t, new(ProjectDTOTestSuite))
}

func (s *ProjectDTOTestSuite) TestProjectCreateIn() {
tests := []struct {
name string
input ProjectCreateIn
expected error
}{
{
name: "it should return error when content is empty",
input: ProjectCreateIn{},
expected: ErrTitleEmpty,
},
{
name: "it should return nil when all fields are valid",
input: ProjectCreateIn{
Title: "project_title",
},
expected: nil,
},
}

for _, test := range tests {
s.Run(test.name, func() {
err := test.input.Validate()
s.Equal(test.expected, err)
})
}
}
52 changes: 28 additions & 24 deletions internal/domain/dto/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (

// TaskCreateIn represents the input of task creation.
type TaskCreateIn struct {
UserID entity.UserID `json:"-"`
Content string `json:"content"`
Description string `json:"description"`
DueDate entity.NullTime `json:"due_date"`
UserID entity.UserID `json:"-"`
ProjectID entity.NullString `json:"project_id"`
Content string `json:"content"`
Description string `json:"description"`
DueDate entity.NullTime `json:"due_date"`
}

func (t *TaskCreateIn) Validate() error {
Expand All @@ -34,13 +35,14 @@ type TaskGetAllIn struct {

// TaskGetAllOut represents the output of task retrieval.
type TaskGetAllOut struct {
ID entity.TaskID `json:"id"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID entity.TaskID `json:"id"`
ProjectID entity.NullString `json:"project_id"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// TaskRemoveIn represents the input of task removal.
Expand All @@ -57,23 +59,25 @@ type TaskGetByIDIn struct {

// TaskGetByIDOut represents the output of task retrieval.
type TaskGetByIDOut struct {
ID entity.TaskID `json:"id"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID entity.TaskID `json:"id"`
ProjectID entity.NullString `json:"project_id"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// TaskUpdateIn represents the input of task update
type TaskUpdateIn struct {
TaskID entity.TaskID `json:"-"`
UserID entity.UserID `json:"-"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
TaskID entity.TaskID `json:"-"`
UserID entity.UserID `json:"-"`
ProjectID entity.NullString `json:"project_id"`
Content string `json:"content"`
Description string `json:"description"`
IsCompleted bool `json:"is_completed"`
DueDate entity.NullTime `json:"due_date"`
}

func (t *TaskUpdateIn) Validate() error {
Expand Down
23 changes: 23 additions & 0 deletions internal/domain/entity/primitive.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ func (t NullTime) MarshalJSON() ([]byte, error) {
}
return json.Marshal(t.Time)
}

type NullString struct {
sql.NullString
}

func (t *NullString) UnmarshalJSON(data []byte) error {
if bytes.Equal(data, nullBytes) {
t.Valid = false
return nil
}
if err := json.Unmarshal(data, &t.String); err != nil {
return err
}
t.Valid = true
return nil
}

func (t NullString) MarshalJSON() ([]byte, error) {
if !t.Valid {
return nullBytes, nil
}
return json.Marshal(t.String)
}
67 changes: 67 additions & 0 deletions internal/domain/entity/primitive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,70 @@ func (s *PrimitiveTestSuite) TestNullTimeMarshalJSON() {
s.Equal(fmt.Sprintf("\"%s\"", currentTime.Format(time.RFC3339Nano)), string(r))
})
}

func (s *PrimitiveTestSuite) TestNullStringUnmarshalJSON() {
s.Run("it should return error when fail to unmarshal with invalid json", func() {
rawJson := `[]`
var str NullString

err := json.Unmarshal([]byte(rawJson), &str)

s.Error(err)
s.False(str.Valid)
s.Empty(str.String)
})

s.Run("it should successfully unmarshal and return valid false and string is zero value", func() {
rawJson := `null`
var str NullString

err := json.Unmarshal([]byte(rawJson), &str)

s.NoError(err)
s.False(str.Valid)
s.Empty(str.String)
})

s.Run("it should successfully unmarshal and return valid true and string is actual string form json", func() {
rawJson := `"Gopher"`
var str NullString

err := json.Unmarshal([]byte(rawJson), &str)

s.NoError(err)
s.True(str.Valid)
s.Equal("Gopher", str.String)
})
}

func (s *PrimitiveTestSuite) TestNullStringMarshalJSON() {
s.Run("it should successfully marshal and return json null when not valid", func() {
str := NullString{
NullString: sql.NullString{
String: "",
Valid: false,
},
}

r, err := json.Marshal(str)

s.NoError(err)
s.Equal("null", string(r))
})

s.Run("it should successfully marshal and return json time correctly", func() {
strValue := "Gopher"

str := NullString{
NullString: sql.NullString{
String: strValue,
Valid: true,
},
}

r, err := json.Marshal(str)

s.NoError(err)
s.Equal(fmt.Sprintf("\"%s\"", strValue), string(r))
})
}
13 changes: 13 additions & 0 deletions internal/domain/entity/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package entity

import "time"

type ProjectID string

type Project struct {
ID ProjectID
UserID UserID
Title string
CreatedAt time.Time
UpdatedAt time.Time
}
1 change: 1 addition & 0 deletions internal/domain/entity/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type TaskID string
type Task struct {
ID TaskID
UserID UserID
ProjectID NullString
Content string
Description string
IsCompleted bool
Expand Down
Loading