├── .gitignore ├── src ├── application │ ├── protocols │ │ ├── hasher.go │ │ ├── remove-book-repository.go │ │ ├── list-books-repository.go │ │ ├── get-book-repository.go │ │ ├── find-book-repository.go │ │ ├── add-book-repository.go │ │ ├── list-accounts-repository.go │ │ ├── add-account-repository.go │ │ └── add-user-repository.go │ └── services │ │ ├── list-users.go │ │ ├── list-books.go │ │ ├── get-book-by-id.go │ │ ├── remove-account.go │ │ ├── delete-user.go │ │ ├── get-user-by-id.go │ │ ├── list-accounts.go │ │ ├── add-user.go │ │ ├── add-book.go │ │ ├── add-account.go │ │ └── remove-book.go ├── presentation │ ├── protocols │ │ ├── validation.go │ │ ├── controller.go │ │ └── http.go │ ├── controllers │ │ ├── list-users.go │ │ ├── list-accounts.go │ │ ├── list-books.go │ │ ├── delete-user.go │ │ ├── remove-book.go │ │ ├── remove-account.go │ │ ├── add-book-by-id.go │ │ ├── get-user-by-id.go │ │ ├── add-user.go │ │ ├── add-account.go │ │ └── add-book.go │ └── helpers │ │ └── http.go ├── domain │ ├── useCases │ │ ├── delete-user.go │ │ ├── remove-account.go │ │ ├── list-books.go │ │ ├── list-users.go │ │ ├── get-book-by-id.go │ │ ├── add-user.go │ │ ├── list-accounts.go │ │ ├── remove-book.go │ │ ├── get-user-by-id.go │ │ ├── add-book.go │ │ └── add-account.go │ ├── entities │ │ ├── base.go │ │ ├── account.go │ │ ├── user.go │ │ └── book.go │ └── domain-dto │ │ ├── remove-book.go │ │ ├── list-accounts.go │ │ ├── add-book.go │ │ ├── get-user-by-id.go │ │ ├── add-user.go │ │ ├── list-users.go │ │ └── add-account.go ├── infrasctructure │ ├── providers │ │ └── hasher.go │ └── repositories │ │ ├── postgres-repository │ │ ├── helper.go │ │ ├── postgres-books.go │ │ ├── postgres-account.go │ │ └── postgres-users.go │ │ ├── in-memory-account.go │ │ ├── in-memory-book.go │ │ └── in-memory-users.go ├── main │ ├── factories │ │ ├── validators │ │ │ ├── add-user.go │ │ │ ├── add-account.go │ │ │ └── add-book.go │ │ ├── list-books.go │ │ ├── list-users.go │ │ ├── delete-user.go │ │ ├── get-book-by-id.go │ │ ├── get-user-by-id-controller.go │ │ ├── remove-account-controller.go │ │ ├── list-accounts.go │ │ ├── add-user-controller.go │ │ ├── remove-book-controller.go │ │ ├── add-book-controller.go │ │ └── add-account-controller.go │ ├── adapters │ │ └── fiber-route.go │ ├── main.go │ └── config │ │ └── routes.go └── validation │ └── validators │ ├── validation-composite.go │ └── required-parameter.go ├── tests ├── presentation │ ├── controller-mocks │ │ └── validator-mock.go │ └── controllers │ │ ├── list_accounts_test.go │ │ ├── list_users_test.go │ │ ├── remove_account_test.go │ │ ├── remove_user_test.go │ │ ├── remove_book_test.go │ │ ├── list_books_test.go │ │ ├── get_book_by_id_test.go │ │ ├── add_account_test.go │ │ ├── get_user_by_id_test.go │ │ ├── add_user_test.go │ │ └── add_book_test.go ├── application │ ├── mocks │ │ ├── hasher.go │ │ ├── remove-book-repository.go │ │ ├── list-users.go │ │ ├── find-book-repository.go │ │ ├── get-book-by-id.go │ │ ├── add-book-repository.go │ │ ├── account-repository.go │ │ └── user-repository.go │ └── services │ │ ├── list_accounts_test.go │ │ ├── remove_account_test.go │ │ ├── get_users_test.go │ │ ├── list_books_test.go │ │ ├── remove_user_test.go │ │ ├── get_user_by_id_test.go │ │ ├── get_book_by_id_test.go │ │ ├── add_user_test.go │ │ ├── add_account_test.go │ │ ├── add_book_test.go │ │ └── remove_book_test.go └── domain │ └── entities │ ├── user_test.go │ ├── account_test.go │ └── book_test.go ├── Dockerfile ├── .github └── workflows │ └── go-test.yml ├── docker-compose.yaml ├── LICENSE ├── go.mod ├── documentation ├── README.md └── golang-api.json ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | -------------------------------------------------------------------------------- /src/application/protocols/hasher.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | type Hasher interface { 4 | Hash(plainText string) string 5 | } 6 | -------------------------------------------------------------------------------- /src/presentation/protocols/validation.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | type Validator interface { 4 | Validate(input any) error 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/useCases/delete-user.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | type DeleteUserUseCase interface { 4 | DeleteUser(userId string) (string, error) 5 | } 6 | -------------------------------------------------------------------------------- /src/presentation/protocols/controller.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | type Controller interface { 4 | Handle(request *HttpRequest) *HttpResponse 5 | } 6 | -------------------------------------------------------------------------------- /src/application/protocols/remove-book-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | type RemoveBookRepository interface { 4 | Remove(bookId string) error 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/useCases/remove-account.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | type RemoveAccountUseCase interface { 4 | RemoveAccount(accountId string) (string, error) 5 | } 6 | -------------------------------------------------------------------------------- /src/domain/useCases/list-books.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type ListBooksUseCase interface { 6 | ListBooks() ([]*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/useCases/list-users.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 4 | 5 | type ListUsersUseCase interface { 6 | List() []*domaindto.ListUsersDTO 7 | } 8 | -------------------------------------------------------------------------------- /src/application/protocols/list-books-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type ListBooksRepository interface { 6 | List() ([]*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/application/protocols/get-book-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type GetBookRepository interface { 6 | Get(bookId string) (*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/useCases/get-book-by-id.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type GetBookByIdUseCase interface { 6 | GetBookById(bookId string) (*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/application/protocols/find-book-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type FindBookRepository interface { 6 | Find(bookId string) (*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /tests/presentation/controller-mocks/validator-mock.go: -------------------------------------------------------------------------------- 1 | package controllermocks_test 2 | 3 | type ValidatorMock struct { 4 | Output error 5 | } 6 | 7 | func (v *ValidatorMock) Validate(input any) error { 8 | return v.Output 9 | } 10 | -------------------------------------------------------------------------------- /src/application/protocols/add-book-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type AddBookRepository interface { 6 | Add(newBook *entities.Book) (*entities.Book, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/application/protocols/list-accounts-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 5 | ) 6 | 7 | type ListAccountsRepository interface { 8 | ListAccounts() []entities.Account 9 | } 10 | -------------------------------------------------------------------------------- /src/domain/useCases/add-user.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 4 | 5 | type AddUserUseCase interface { 6 | Add(input *domaindto.AddUserInputDTO) (*domaindto.AddUserOutputDTO, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/useCases/list-accounts.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 5 | ) 6 | 7 | type ListAccountsUseCase interface { 8 | ListAccounts() []domaindto.ListAccountsOutputDTO 9 | } 10 | -------------------------------------------------------------------------------- /src/domain/useCases/remove-book.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 4 | 5 | type RemoveBookUseCase interface { 6 | RemoveBook(bookId string) (*domaindto.RemoveBookUseCaseOutputDTO, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/entities/base.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "time" 4 | 5 | type Base struct { 6 | ID string `json:"id" valid:"uuid"` 7 | CreatedAt time.Time `json:"created_at" valid:"-"` 8 | UpdatedAt time.Time `json:"updated_at" valid:"-"` 9 | } 10 | -------------------------------------------------------------------------------- /src/domain/useCases/get-user-by-id.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 4 | 5 | type GetUserByIdUseCase interface { 6 | GetUserById(userId string) (*domaindto.GetUserByIdUseCaseOutputDTO, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/useCases/add-book.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 4 | 5 | type AddBookUseCase interface { 6 | AddBook(input *domaindto.AddBookUseCaseInputDTO) (*domaindto.AddBookUseCaseOutputDTO, error) 7 | } 8 | -------------------------------------------------------------------------------- /src/domain/useCases/add-account.go: -------------------------------------------------------------------------------- 1 | package usecases 2 | 3 | import ( 4 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 5 | ) 6 | 7 | type AddAccountUseCase interface { 8 | AddAccount(input *domaindto.AddAccountInputDTO) (*domaindto.AddAccountOutputDTO, error) 9 | } 10 | -------------------------------------------------------------------------------- /src/domain/domain-dto/remove-book.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | type RemoveBookUseCaseOutputDTO struct { 4 | Title string `json:"title"` 5 | Author string `json:"author"` 6 | Description string `json:"description"` 7 | Price float64 `json:"price"` 8 | UserId string `json:"userId"` 9 | } 10 | -------------------------------------------------------------------------------- /tests/application/mocks/hasher.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | type HasherSpy struct { 4 | Input string 5 | } 6 | 7 | func (hasher *HasherSpy) Hash(plainText string) string { 8 | hasher.Input = plainText 9 | return "hashed_text" 10 | } 11 | 12 | func NewHasherSpy() *HasherSpy { 13 | return &HasherSpy{} 14 | } 15 | -------------------------------------------------------------------------------- /src/domain/domain-dto/list-accounts.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | import "time" 4 | 5 | type ListAccountsOutputDTO struct { 6 | ID string `json:"id" valid:"uuid"` 7 | CreatedAt time.Time `json:"createdAt" valid:"-"` 8 | UpdatedAt time.Time `json:"updatedAt" valid:"-"` 9 | UserName string `json:"userName"` 10 | Email string `json:"email"` 11 | } 12 | -------------------------------------------------------------------------------- /src/application/protocols/add-account-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type AccountRepository interface { 6 | CheckAccountByEmail(email string) bool 7 | CheckAccountByUserName(userName string) bool 8 | Save(account *entities.Account) error 9 | DeleteAccountById(accountId string) bool 10 | } 11 | -------------------------------------------------------------------------------- /src/presentation/protocols/http.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | type HttpResponse struct { 4 | StatusCode int 5 | Body any 6 | } 7 | 8 | type HttpRequest struct { 9 | Params []byte 10 | Body []byte 11 | } 12 | 13 | func NewHttpRequest(body []byte, params []byte) *HttpRequest { 14 | return &HttpRequest{ 15 | Body: body, 16 | Params: params, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM golang:1.18.3-alpine3.16 AS build-stage 3 | 4 | WORKDIR /users-api 5 | 6 | ADD ./src ./src 7 | COPY ./go.mod . 8 | COPY ./go.sum . 9 | 10 | RUN ["go", "build", "./src/main/main.go"] 11 | 12 | # run stage 13 | FROM alpine 14 | 15 | WORKDIR /api 16 | 17 | COPY --from=build-stage /users-api/main /api 18 | 19 | EXPOSE 3333 20 | 21 | CMD ["./main"] 22 | -------------------------------------------------------------------------------- /tests/application/mocks/remove-book-repository.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | type RemoveBookRepositorySpy struct { 4 | RemoveInput string 5 | RemoveError error 6 | } 7 | 8 | func (repo *RemoveBookRepositorySpy) Remove(bookId string) error { 9 | repo.RemoveInput = bookId 10 | return repo.RemoveError 11 | } 12 | 13 | func NewRemoveBookRepositorySpy() *RemoveBookRepositorySpy { 14 | return &RemoveBookRepositorySpy{} 15 | } 16 | -------------------------------------------------------------------------------- /src/infrasctructure/providers/hasher.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | type BcryptHasher struct{} 6 | 7 | func (hasher *BcryptHasher) Hash(plainText string) string { 8 | hashed, err := bcrypt.GenerateFromPassword([]byte(plainText), 10) 9 | if err != nil { 10 | panic(err) 11 | } 12 | return string(hashed) 13 | } 14 | 15 | func NewBcryptHasher() *BcryptHasher { 16 | return &BcryptHasher{} 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/go-test.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "main", "refactoring" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.18 19 | 20 | - name: Test code 21 | run: go test -v ./... 22 | -------------------------------------------------------------------------------- /src/application/protocols/add-user-repository.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type UserRepository interface { 6 | Save(user *entities.User) error 7 | CheckByEmail(email string) bool 8 | CheckByUserName(userName string) bool 9 | List() []*entities.User 10 | Delete(userId string) error 11 | CheckById(userId string) bool 12 | GetById(userId string) (*entities.User, error) 13 | } 14 | -------------------------------------------------------------------------------- /tests/application/mocks/list-users.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type ListBooksRepositoryStub struct { 6 | Output []*entities.Book 7 | OutputError error 8 | } 9 | 10 | func (repo *ListBooksRepositoryStub) List() ([]*entities.Book, error) { 11 | return repo.Output, repo.OutputError 12 | } 13 | 14 | func NewListBooksRepositoryStub() *ListBooksRepositoryStub { 15 | return &ListBooksRepositoryStub{} 16 | } 17 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | db: 5 | image: postgres 6 | ports: 7 | - 5432:5432 8 | restart: always 9 | environment: 10 | POSTGRES_PASSWORD: root 11 | POSTGRES_DB: users 12 | healthcheck: 13 | test: ["CMD-SHELL", "pg_isready -U postgres"] 14 | interval: 3s 15 | timeout: 1s 16 | retries: 20 17 | networks: 18 | - go-api-network 19 | 20 | networks: 21 | go-api-network: 22 | driver: bridge 23 | 24 | -------------------------------------------------------------------------------- /tests/application/mocks/find-book-repository.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type FindBookRepositorySpy struct { 6 | FindInput string 7 | FindOutput *entities.Book 8 | FindError error 9 | } 10 | 11 | func (repo *FindBookRepositorySpy) Find(bookId string) (*entities.Book, error) { 12 | repo.FindInput = bookId 13 | return repo.FindOutput, repo.FindError 14 | } 15 | 16 | func NewFindBookRepositorySpy() *FindBookRepositorySpy { 17 | return &FindBookRepositorySpy{} 18 | } 19 | -------------------------------------------------------------------------------- /tests/application/mocks/get-book-by-id.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type GetBookByIdRepositorySpy struct { 6 | Input string 7 | Output *entities.Book 8 | OutputError error 9 | } 10 | 11 | func (repo *GetBookByIdRepositorySpy) Get(bookId string) (*entities.Book, error) { 12 | repo.Input = bookId 13 | return repo.Output, repo.OutputError 14 | } 15 | 16 | func NewGetBookByIdRepositorySpy() *GetBookByIdRepositorySpy { 17 | return &GetBookByIdRepositorySpy{} 18 | } 19 | -------------------------------------------------------------------------------- /src/domain/domain-dto/add-book.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | type AddBookUseCaseInputDTO struct { 4 | Title string `json:"title"` 5 | Author string `json:"author"` 6 | Price float64 `json:"price"` 7 | Description string `json:"description"` 8 | UserId string `json:"userId"` 9 | } 10 | 11 | type AddBookUseCaseOutputDTO struct { 12 | ID string `json:"id"` 13 | Title string `json:"title"` 14 | Author string `json:"author"` 15 | Price float64 `json:"price"` 16 | Description string `json:"description"` 17 | } 18 | -------------------------------------------------------------------------------- /tests/application/mocks/add-book-repository.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type AddBookRepositorySpy struct { 6 | Input *entities.Book 7 | Output *entities.Book 8 | OutputError error 9 | } 10 | 11 | func (repo *AddBookRepositorySpy) Add(newBook *entities.Book) (*entities.Book, error) { 12 | repo.Input = newBook 13 | repo.Output = repo.Input 14 | return repo.Output, repo.OutputError 15 | } 16 | 17 | func NewAddBookRepositorySpy() *AddBookRepositorySpy { 18 | return &AddBookRepositorySpy{} 19 | } 20 | -------------------------------------------------------------------------------- /src/application/services/list-users.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 5 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 6 | ) 7 | 8 | type ListUsersService struct { 9 | userRepository protocols.UserRepository 10 | } 11 | 12 | func NewListUsersService(repo protocols.UserRepository) *ListUsersService { 13 | return &ListUsersService{ 14 | userRepository: repo, 15 | } 16 | } 17 | 18 | func (s *ListUsersService) List() []*domaindto.ListUsersDTO { 19 | users := s.userRepository.List() 20 | return domaindto.MapListUsersDTO(users) 21 | } 22 | -------------------------------------------------------------------------------- /src/main/factories/validators/add-user.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 5 | "github.com/KPMGE/go-users-clean-api/src/validation/validators" 6 | ) 7 | 8 | func MakeAddUserValidation() protocols.Validator { 9 | userNameFiled := validators.NewRequiredParameterValidation("UserName") 10 | nameFiled := validators.NewRequiredParameterValidation("Name") 11 | emailField := validators.NewRequiredParameterValidation("Email") 12 | composite := validators.NewValidationComposite([]protocols.Validator{userNameFiled, emailField, nameFiled }) 13 | return composite 14 | } 15 | -------------------------------------------------------------------------------- /src/main/factories/list-books.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeListBooksController(db *gorm.DB) *controllers.ListBooksController { 11 | repo := postgresrepository.NewPostgresBookRepository(db) 12 | services := services.NewListBookService(repo) 13 | controller := controllers.NewListBooksController(services) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/main/factories/list-users.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeListUsersController(db *gorm.DB) *controllers.ListUsersController { 11 | repo := postgresrepository.NewPostgresUserRepository(db) 12 | service := services.NewListUsersService(repo) 13 | controller := controllers.NewListUsersController(service) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/main/factories/delete-user.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeDeleteUserController(db *gorm.DB) *controllers.DeleteUserController { 11 | repo := postgresrepository.NewPostgresUserRepository(db) 12 | service := services.NewDeleteUserService(repo) 13 | controller := controllers.NewDeleteUserController(service) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/validation/validators/validation-composite.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 4 | 5 | type ValidationComposite struct { 6 | validators []protocols.Validator 7 | } 8 | 9 | func (v *ValidationComposite) Validate(input any) error { 10 | for _, validator := range v.validators { 11 | err := validator.Validate(input) 12 | if err != nil { 13 | return err 14 | } 15 | } 16 | return nil 17 | } 18 | 19 | func NewValidationComposite(validators []protocols.Validator) *ValidationComposite { 20 | return &ValidationComposite{ 21 | validators: validators, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/factories/get-book-by-id.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeGetBookByIdController(db *gorm.DB) *controllers.GetBookByIdController { 11 | repo := postgresrepository.NewPostgresBookRepository(db) 12 | service := services.NewGetBookByIdService(repo) 13 | controller := controllers.NewGetBookByIdController(service) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/main/factories/get-user-by-id-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeGetUserByIdController(db *gorm.DB) *controllers.GetUserByIdController { 11 | repo := postgresrepository.NewPostgresUserRepository(db) 12 | service := services.NewGetUserByIdService(repo) 13 | controller := controllers.NewGetUserByIdController(service) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/application/services/list-books.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 5 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 6 | ) 7 | 8 | type ListBooksService struct { 9 | listBooksRepo protocols.ListBooksRepository 10 | } 11 | 12 | func NewListBookService(repo protocols.ListBooksRepository) *ListBooksService { 13 | return &ListBooksService{ 14 | listBooksRepo: repo, 15 | } 16 | } 17 | 18 | func (s *ListBooksService) ListBooks() ([]*entities.Book, error) { 19 | books, err := s.listBooksRepo.List() 20 | if err != nil { 21 | return nil, err 22 | } 23 | return books, nil 24 | } 25 | -------------------------------------------------------------------------------- /src/main/factories/remove-account-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func MakeRemoveAccountController(db *gorm.DB) *controllers.RemoveAccountController { 11 | repo := postgresrepository.NewPostgresAccountRepository(db) 12 | useCase := services.NewRemoveAccountService(repo) 13 | controller := controllers.NewRemoveAccountController(useCase) 14 | return controller 15 | } 16 | -------------------------------------------------------------------------------- /src/application/services/get-book-by-id.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 5 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 6 | ) 7 | 8 | type GetBookByIdService struct { 9 | getBookRepo protocols.GetBookRepository 10 | } 11 | 12 | func (s *GetBookByIdService) GetBookById(bookId string) (*entities.Book, error) { 13 | book, err := s.getBookRepo.Get(bookId) 14 | if err != nil { 15 | return nil, err 16 | } 17 | return book, nil 18 | } 19 | 20 | func NewGetBookByIdService(getBookRepo protocols.GetBookRepository) *GetBookByIdService { 21 | return &GetBookByIdService{ 22 | getBookRepo: getBookRepo, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/factories/list-accounts.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func MakeListAccountsController(db *gorm.DB) protocols.Controller { 12 | repo := postgresrepository.NewPostgresAccountRepository(db) 13 | service := services.NewListAccountsService(repo) 14 | controller := controllers.NewListAccountsController(service) 15 | return controller 16 | } 17 | -------------------------------------------------------------------------------- /src/domain/domain-dto/get-user-by-id.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type GetUserByIdUseCaseOutputDTO struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | Email string `json:"email"` 9 | UserName string `json:"userName"` 10 | Books []entities.Book `json:"books"` 11 | } 12 | 13 | func NewGetUserByIdUseCaseOutputDTO(id string, name string, email string, userName string, books []entities.Book) *GetUserByIdUseCaseOutputDTO { 14 | return &GetUserByIdUseCaseOutputDTO{ 15 | ID: id, 16 | Email: email, 17 | Name: name, 18 | UserName: userName, 19 | Books: books, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/presentation/controllers/list-users.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type ListUsersController struct { 10 | service usecases.ListUsersUseCase 11 | } 12 | 13 | func (controller *ListUsersController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 14 | users := controller.service.List() 15 | return helpers.Ok(users) 16 | } 17 | 18 | func NewListUsersController(service usecases.ListUsersUseCase) *ListUsersController { 19 | return &ListUsersController{ 20 | service: service, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/application/services/remove-account.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 6 | ) 7 | 8 | type RemoveAccountService struct { 9 | accountRepository protocols.AccountRepository 10 | } 11 | 12 | func (useCase *RemoveAccountService) RemoveAccount(accountId string) (string, error) { 13 | foundAccount := useCase.accountRepository.DeleteAccountById(accountId) 14 | if !foundAccount { 15 | return "", errors.New("there is no account with this id") 16 | } 17 | return "account deleted", nil 18 | } 19 | 20 | func NewRemoveAccountService(repo protocols.AccountRepository) *RemoveAccountService { 21 | return &RemoveAccountService{ 22 | accountRepository: repo, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/factories/validators/add-account.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 5 | "github.com/KPMGE/go-users-clean-api/src/validation/validators" 6 | ) 7 | 8 | func MakeAddAccountValidation() protocols.Validator { 9 | userNameFiled := validators.NewRequiredParameterValidation("UserName") 10 | emailField := validators.NewRequiredParameterValidation("Email") 11 | passwordField := validators.NewRequiredParameterValidation("Password") 12 | confirmPasswordField := validators.NewRequiredParameterValidation("ConfirmPassword") 13 | composite := validators.NewValidationComposite([]protocols.Validator{userNameFiled, emailField, passwordField, confirmPasswordField}) 14 | return composite 15 | } 16 | -------------------------------------------------------------------------------- /src/presentation/controllers/list-accounts.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type ListAccountsController struct { 10 | service usecases.ListAccountsUseCase 11 | } 12 | 13 | func NewListAccountsController(service usecases.ListAccountsUseCase) *ListAccountsController { 14 | return &ListAccountsController{ 15 | service: service, 16 | } 17 | } 18 | 19 | func (c *ListAccountsController) Handle(req *protocols.HttpRequest) *protocols.HttpResponse { 20 | accounts := c.service.ListAccounts() 21 | 22 | return helpers.Ok(accounts) 23 | } 24 | -------------------------------------------------------------------------------- /src/main/factories/add-user-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/main/factories/validators" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func MakeAddUserController(db *gorm.DB) *controllers.AddUserController { 12 | repo := postgresrepository.NewPostgresUserRepository(db) 13 | service := services.NewAddUserService(repo) 14 | validator := validators.MakeAddUserValidation() 15 | controller := controllers.NewAddUserController(service, validator) 16 | return controller 17 | } 18 | -------------------------------------------------------------------------------- /src/application/services/delete-user.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 6 | ) 7 | 8 | type DeleteUserService struct { 9 | userRepository protocols.UserRepository 10 | } 11 | 12 | func (s *DeleteUserService) DeleteUser(userId string) (string, error) { 13 | userExists := s.userRepository.CheckById(userId) 14 | if !userExists { 15 | return "", errors.New("there is no user with the provided id") 16 | } 17 | err := s.userRepository.Delete(userId) 18 | if err != nil { 19 | return "", err 20 | } 21 | return "user deleted successfully", nil 22 | } 23 | 24 | func NewDeleteUserService(repo protocols.UserRepository) *DeleteUserService { 25 | return &DeleteUserService{ 26 | userRepository: repo, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/factories/remove-book-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories" 6 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func MakeRemoveBookController(db *gorm.DB) *controllers.RemoveBookController { 12 | repo := postgresrepository.NewPostgresBookRepository(db) 13 | userRepo := repositories.NewInMemoryUserRepository() 14 | service := services.NewRemoveBookService(repo, repo, userRepo) 15 | controller := controllers.NewRemoveBookController(service) 16 | return controller 17 | } 18 | -------------------------------------------------------------------------------- /src/presentation/controllers/list-books.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type ListBooksController struct { 10 | service usecases.ListBooksUseCase 11 | } 12 | 13 | func (controller *ListBooksController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 14 | books, err := controller.service.ListBooks() 15 | if err != nil { 16 | return helpers.ServerError(err) 17 | } 18 | 19 | return helpers.Ok(books) 20 | } 21 | 22 | func NewListBooksController(service usecases.ListBooksUseCase) *ListBooksController { 23 | return &ListBooksController{ 24 | service: service, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/presentation/controllers/delete-user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type DeleteUserController struct { 10 | service usecases.DeleteUserUseCase 11 | } 12 | 13 | func NewDeleteUserController(service usecases.DeleteUserUseCase) *DeleteUserController { 14 | return &DeleteUserController{ 15 | service: service, 16 | } 17 | } 18 | 19 | func (controller *DeleteUserController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 20 | message, err := controller.service.DeleteUser(string(request.Params)) 21 | if err != nil { 22 | return helpers.BadRequest(err) 23 | } 24 | return helpers.Ok(message) 25 | } 26 | -------------------------------------------------------------------------------- /src/domain/domain-dto/add-user.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | type AddUserInputDTO struct { 4 | Name string `json:"name"` 5 | UserName string `json:"userName"` 6 | Email string `json:"email"` 7 | } 8 | 9 | func NewAddUserInputDTO(name string, userName string, email string) *AddUserInputDTO { 10 | return &AddUserInputDTO{ 11 | Name: name, 12 | Email: email, 13 | UserName: userName, 14 | } 15 | } 16 | 17 | type AddUserOutputDTO struct { 18 | ID string `json:"id"` 19 | Name string `json:"name"` 20 | UserName string `json:"userName"` 21 | Email string `json:"email"` 22 | } 23 | 24 | func NewAddUserOutputDTO(id string, name string, userName string, email string) *AddUserOutputDTO { 25 | return &AddUserOutputDTO{ 26 | Name: name, 27 | Email: email, 28 | ID: id, 29 | UserName: userName, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/factories/add-book-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 6 | "github.com/KPMGE/go-users-clean-api/src/main/factories/validators" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func MakeAddBookController(db *gorm.DB) *controllers.AddBookController { 12 | bookRepo := postgresrepository.NewPostgresBookRepository(db) 13 | userRepo := postgresrepository.NewPostgresUserRepository(db) 14 | service := services.NewAddBookService(bookRepo, userRepo) 15 | validator := validators.MakeAddBookValidation() 16 | controller := controllers.NewAddBookController(service, validator) 17 | return controller 18 | } 19 | -------------------------------------------------------------------------------- /src/main/factories/validators/add-book.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 5 | "github.com/KPMGE/go-users-clean-api/src/validation/validators" 6 | ) 7 | 8 | func MakeAddBookValidation() protocols.Validator { 9 | userIdField := validators.NewRequiredParameterValidation("UserId") 10 | titleField := validators.NewRequiredParameterValidation("Title") 11 | authorField := validators.NewRequiredParameterValidation("Author") 12 | descriptionField := validators.NewRequiredParameterValidation("Description") 13 | priceField := validators.NewRequiredParameterValidation("Price") 14 | composite := validators.NewValidationComposite([]protocols.Validator{ 15 | titleField, 16 | userIdField, 17 | authorField, 18 | descriptionField, 19 | priceField, 20 | }) 21 | return composite 22 | } 23 | -------------------------------------------------------------------------------- /src/presentation/controllers/remove-book.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type RemoveBookController struct { 10 | service usecases.RemoveBookUseCase 11 | } 12 | 13 | func NewRemoveBookController(service usecases.RemoveBookUseCase) *RemoveBookController { 14 | return &RemoveBookController{ 15 | service: service, 16 | } 17 | } 18 | 19 | func (controller *RemoveBookController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 20 | removedBook, err := controller.service.RemoveBook(string(request.Params)) 21 | if err != nil { 22 | return helpers.BadRequest(err) 23 | } 24 | return helpers.Ok(removedBook) 25 | } 26 | -------------------------------------------------------------------------------- /src/presentation/controllers/remove-account.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 5 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | type RemoveAccountController struct { 10 | useCase usecases.RemoveAccountUseCase 11 | } 12 | 13 | func (controller *RemoveAccountController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 14 | message, err := controller.useCase.RemoveAccount(string(request.Params)) 15 | if err != nil { 16 | return helpers.BadRequest(err) 17 | } 18 | return helpers.Ok(message) 19 | } 20 | 21 | func NewRemoveAccountController(useCase usecases.RemoveAccountUseCase) *RemoveAccountController { 22 | return &RemoveAccountController{ 23 | useCase: useCase, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/presentation/helpers/http.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 7 | ) 8 | 9 | func Ok(data any) *protocols.HttpResponse { 10 | return &protocols.HttpResponse{ 11 | StatusCode: http.StatusOK, 12 | Body: data, 13 | } 14 | } 15 | 16 | func BadRequest(err error) *protocols.HttpResponse { 17 | return &protocols.HttpResponse{ 18 | StatusCode: http.StatusBadRequest, 19 | Body: err.Error(), 20 | } 21 | } 22 | 23 | func NotFound(err error) *protocols.HttpResponse { 24 | return &protocols.HttpResponse{ 25 | StatusCode: http.StatusNotFound, 26 | Body: err.Error(), 27 | } 28 | } 29 | 30 | func ServerError(err error) *protocols.HttpResponse { 31 | return &protocols.HttpResponse{ 32 | StatusCode: http.StatusInternalServerError, 33 | Body: err.Error(), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/application/services/list_accounts_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/services" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type ListAccountsRepositoryStub struct { 13 | Output []entities.Account 14 | } 15 | 16 | func (l *ListAccountsRepositoryStub) ListAccounts() []entities.Account { 17 | return l.Output 18 | } 19 | 20 | func TestListAccounts_ShouldReturnFromRepository(t *testing.T) { 21 | repo := &ListAccountsRepositoryStub{ 22 | Output: []entities.Account{}, 23 | } 24 | sut := services.NewListAccountsService(repo) 25 | 26 | result := sut.ListAccounts() 27 | 28 | require.Equal(t, []domaindto.ListAccountsOutputDTO{}, result) 29 | } 30 | -------------------------------------------------------------------------------- /src/main/factories/add-account-controller.go: -------------------------------------------------------------------------------- 1 | package factories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | "github.com/KPMGE/go-users-clean-api/src/infrasctructure/providers" 6 | postgresrepository "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories/postgres-repository" 7 | "github.com/KPMGE/go-users-clean-api/src/main/factories/validators" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | func MakeAddAccountController(db *gorm.DB) *controllers.AddAccountController { 13 | repo := postgresrepository.NewPostgresAccountRepository(db) 14 | hasher := providers.NewBcryptHasher() 15 | service := services.NewAddAccountService(repo, hasher) 16 | validator := validators.MakeAddAccountValidation() 17 | controller := controllers.NewAddAccountController(service, validator) 18 | return controller 19 | } 20 | -------------------------------------------------------------------------------- /src/domain/domain-dto/list-users.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type ListUsersDTO struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | UserName string `json:"userName"` 9 | Email string `json:"email"` 10 | Books []entities.Book `json:"books"` 11 | } 12 | 13 | func NewListUserDTO(id string, name string, userName string, email string, books []entities.Book) *ListUsersDTO { 14 | return &ListUsersDTO{ 15 | ID: id, 16 | Name: name, 17 | UserName: userName, 18 | Email: email, 19 | Books: books, 20 | } 21 | } 22 | 23 | func MapListUsersDTO(users []*entities.User) []*ListUsersDTO { 24 | list := []*ListUsersDTO{} 25 | for _, user := range users { 26 | list = append(list, NewListUserDTO(user.ID, user.Name, user.UserName, user.Email, user.Books)) 27 | } 28 | return list 29 | } 30 | -------------------------------------------------------------------------------- /tests/presentation/controllers/list_accounts_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type ListAccountsServiceStub struct { 13 | Output []domaindto.ListAccountsOutputDTO 14 | } 15 | 16 | func (l *ListAccountsServiceStub) ListAccounts() []domaindto.ListAccountsOutputDTO { 17 | return l.Output 18 | } 19 | 20 | func TestListAccountsController_ShouldReturnFromService(t *testing.T) { 21 | service := &ListAccountsServiceStub{ 22 | Output: []domaindto.ListAccountsOutputDTO{}, 23 | } 24 | sut := controllers.NewListAccountsController(service) 25 | 26 | httpResponse := sut.Handle(nil) 27 | 28 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 29 | require.NotNil(t, httpResponse.Body) 30 | } 31 | -------------------------------------------------------------------------------- /src/domain/domain-dto/add-account.go: -------------------------------------------------------------------------------- 1 | package domaindto 2 | 3 | type AddAccountOutputDTO struct { 4 | ID string `json:"id"` 5 | UserName string `json:"userName"` 6 | Email string `json:"email"` 7 | } 8 | 9 | type AddAccountInputDTO struct { 10 | UserName string `json:"userName"` 11 | Email string `json:"email"` 12 | Password string `json:"password"` 13 | ConfirmPassword string `json:"confirmPassword"` 14 | } 15 | 16 | func NewAddAccountInputDTO(userName string, email string, password string, confirmPassword string) *AddAccountInputDTO { 17 | return &AddAccountInputDTO{ 18 | UserName: userName, 19 | Email: email, 20 | Password: password, 21 | ConfirmPassword: confirmPassword, 22 | } 23 | } 24 | 25 | func NewAddAccountOutputDTO(id string, userName string, email string) *AddAccountOutputDTO { 26 | return &AddAccountOutputDTO{ 27 | ID: id, 28 | UserName: userName, 29 | Email: email, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/presentation/controllers/add-book-by-id.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | 6 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 9 | ) 10 | 11 | type GetBookByIdController struct { 12 | service usecases.GetBookByIdUseCase 13 | } 14 | 15 | func NewGetBookByIdController(service usecases.GetBookByIdUseCase) *GetBookByIdController { 16 | return &GetBookByIdController{ 17 | service: service, 18 | } 19 | } 20 | 21 | func (controller *GetBookByIdController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 22 | book, err := controller.service.GetBookById(string(request.Params)) 23 | if err != nil { 24 | return helpers.ServerError(err) 25 | } 26 | 27 | if book == nil { 28 | return helpers.NotFound(errors.New("book not found")) 29 | } 30 | 31 | return helpers.Ok(book) 32 | } 33 | -------------------------------------------------------------------------------- /tests/application/mocks/account-repository.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type FakeAccountRepository struct { 6 | Input *entities.Account 7 | CheckAccountOutput bool 8 | CheckUserNameOutput bool 9 | DeleteAccountByIdOutput bool 10 | } 11 | 12 | func (repo *FakeAccountRepository) CheckAccountByEmail(email string) bool { 13 | return repo.CheckAccountOutput 14 | } 15 | 16 | func (repo *FakeAccountRepository) CheckAccountByUserName(userName string) bool { 17 | return repo.CheckUserNameOutput 18 | } 19 | 20 | func (repo *FakeAccountRepository) Save(account *entities.Account) error { 21 | repo.Input = account 22 | return nil 23 | } 24 | 25 | func (repo *FakeAccountRepository) DeleteAccountById(accountId string) bool { 26 | return repo.DeleteAccountByIdOutput 27 | } 28 | 29 | func NewFakeAccountRepository() *FakeAccountRepository { 30 | return &FakeAccountRepository{} 31 | } 32 | -------------------------------------------------------------------------------- /src/main/adapters/fiber-route.go: -------------------------------------------------------------------------------- 1 | package adapters 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 5 | "github.com/gofiber/fiber/v2" 6 | ) 7 | 8 | func FiberRouteAdapter(controller protocols.Controller) func(c *fiber.Ctx) error { 9 | return func(c *fiber.Ctx) error { 10 | params := c.Route().Params 11 | if params == nil { 12 | request := protocols.NewHttpRequest(c.Body(), nil) 13 | httpResponse := controller.Handle(request) 14 | return c.Status(httpResponse.StatusCode).JSON(httpResponse.Body) 15 | } 16 | paramName := params[0] 17 | paramValue := c.Params(paramName) 18 | request := protocols.NewHttpRequest(c.Body(), []byte(paramValue)) 19 | httpResponse := controller.Handle(request) 20 | 21 | if httpResponse.StatusCode < 299 { 22 | return c.Status(httpResponse.StatusCode).JSON(httpResponse.Body) 23 | } 24 | return c.Status(httpResponse.StatusCode).JSON(map[string]any{ 25 | "error": httpResponse.Body, 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/application/services/get-user-by-id.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | ) 9 | 10 | type GetUserByIdService struct { 11 | userRepository protocols.UserRepository 12 | } 13 | 14 | func NewGetUserByIdService(repo protocols.UserRepository) *GetUserByIdService { 15 | return &GetUserByIdService{ 16 | userRepository: repo, 17 | } 18 | } 19 | 20 | func (s *GetUserByIdService) GetUserById(userId string) (*domaindto.GetUserByIdUseCaseOutputDTO, error) { 21 | foundUser, err := s.userRepository.GetById(userId) 22 | 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if foundUser == nil { 28 | return nil, errors.New("User not found!") 29 | } 30 | 31 | output := domaindto.NewGetUserByIdUseCaseOutputDTO( 32 | foundUser.ID, 33 | foundUser.Name, 34 | foundUser.Email, 35 | foundUser.UserName, 36 | foundUser.Books, 37 | ) 38 | 39 | return output, nil 40 | } 41 | -------------------------------------------------------------------------------- /src/application/services/list-accounts.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 5 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 6 | ) 7 | 8 | type ListAccountsService struct { 9 | accountsRepo protocols.ListAccountsRepository 10 | } 11 | 12 | func NewListAccountsService(repo protocols.ListAccountsRepository) *ListAccountsService { 13 | return &ListAccountsService{ 14 | accountsRepo: repo, 15 | } 16 | } 17 | 18 | func (l *ListAccountsService) ListAccounts() []domaindto.ListAccountsOutputDTO { 19 | foundAccounts := l.accountsRepo.ListAccounts() 20 | 21 | accountsDto := []domaindto.ListAccountsOutputDTO{} 22 | 23 | for _, account := range foundAccounts { 24 | accountsDto = append(accountsDto, domaindto.ListAccountsOutputDTO{ 25 | UserName: account.UserName, 26 | Email: account.Email, 27 | ID: account.ID, 28 | CreatedAt: account.CreatedAt, 29 | UpdatedAt: account.UpdatedAt, 30 | }) 31 | } 32 | 33 | return accountsDto 34 | } 35 | -------------------------------------------------------------------------------- /src/validation/validators/required-parameter.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | type RequiredParameterValidation struct { 10 | parameterName string 11 | } 12 | 13 | func getAttr(obj interface{}, fieldName string) reflect.Value { 14 | pointToStruct := reflect.ValueOf(obj) // addressable 15 | curStruct := pointToStruct.Elem() 16 | if curStruct.Kind() != reflect.Struct { 17 | panic("not a struct") 18 | } 19 | curField := curStruct.FieldByName(fieldName) // type: reflect.Value 20 | if !curField.IsValid() { 21 | panic("not found:" + fieldName) 22 | } 23 | return curField 24 | } 25 | 26 | func (r *RequiredParameterValidation) Validate(input any) error { 27 | val := getAttr(input, r.parameterName) 28 | if val.IsZero() { 29 | return errors.New(fmt.Sprintf("Missing field %s!", r.parameterName)) 30 | } 31 | return nil 32 | } 33 | 34 | func NewRequiredParameterValidation(fieldName string) *RequiredParameterValidation { 35 | return &RequiredParameterValidation{ 36 | parameterName: fieldName, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/presentation/controllers/list_users_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | type ListUsersServiceMock struct { 13 | Output []*domaindto.ListUsersDTO 14 | } 15 | 16 | func (l *ListUsersServiceMock) List() []*domaindto.ListUsersDTO { 17 | return l.Output 18 | } 19 | 20 | func MakeListUsersSut() (*controllers.ListUsersController, *ListUsersServiceMock) { 21 | serviceMock := ListUsersServiceMock{Output: []*domaindto.ListUsersDTO{}} 22 | sut := controllers.NewListUsersController(&serviceMock) 23 | return sut, &serviceMock 24 | } 25 | 26 | func TestListUsersController_ShouldReturnFromService(t *testing.T) { 27 | sut, serviceMock := MakeListUsersSut() 28 | 29 | httpResponse := sut.Handle(nil) 30 | 31 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 32 | require.NotNil(t, serviceMock.Output, httpResponse.Body) 33 | } 34 | -------------------------------------------------------------------------------- /src/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 8 | configuration "github.com/KPMGE/go-users-clean-api/src/main/config" 9 | "github.com/gofiber/fiber/v2" 10 | _ "github.com/lib/pq" 11 | "gorm.io/driver/postgres" 12 | "gorm.io/gorm" 13 | ) 14 | 15 | func CheckError(err error) { 16 | if err != nil { 17 | log.Fatalln(err) 18 | } 19 | } 20 | 21 | func Init() *gorm.DB { 22 | dbURL := "postgresql://postgres:root@localhost:5432/users" 23 | 24 | db, err := gorm.Open(postgres.Open(dbURL), &gorm.Config{}) 25 | 26 | if err != nil { 27 | log.Fatalln(err) 28 | } 29 | 30 | fmt.Println("Migrating entities...") 31 | err = db.AutoMigrate(&entities.Book{}) 32 | CheckError(err) 33 | err = db.AutoMigrate(&entities.Account{}) 34 | CheckError(err) 35 | err = db.AutoMigrate(&entities.User{}) 36 | CheckError(err) 37 | 38 | return db 39 | } 40 | 41 | func main() { 42 | db := Init() 43 | // db1 := postgresrepository.GetPostgresConnection() 44 | app := fiber.New() 45 | configuration.SetupRoutes(app, db) 46 | app.Listen(":3333") 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kevin Carvalho de Jesus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/domain/entities/account.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/asaskevich/govalidator" 8 | uuid "github.com/satori/go.uuid" 9 | ) 10 | 11 | type Account struct { 12 | Base 13 | UserName string `json:"user_name" valid:"required"` 14 | Email string `json:"email" valid:"required"` 15 | Password string `json:"password" valid:"required"` 16 | } 17 | 18 | func (account *Account) isValid() error { 19 | validEmail := govalidator.IsEmail(account.Email) 20 | if !validEmail { 21 | return errors.New("Invalid email!") 22 | } 23 | 24 | _, err := govalidator.ValidateStruct(account) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func NewAccount(userName string, email string, password string) (*Account, error) { 33 | account := Account{ 34 | Email: email, 35 | UserName: userName, 36 | Password: password, 37 | } 38 | 39 | account.CreatedAt = time.Now() 40 | account.UpdatedAt = time.Now() 41 | account.ID = uuid.NewV4().String() 42 | 43 | err := account.isValid() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &account, nil 49 | } 50 | -------------------------------------------------------------------------------- /src/domain/entities/user.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | "github.com/asaskevich/govalidator" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | type User struct { 11 | Base 12 | Name string `json:"name" valid:"required"` 13 | UserName string `json:"user_name" valid:"required"` 14 | Email string `json:"email" valid:"email"` 15 | Books []Book `json:"books" valid:"-"` 16 | } 17 | 18 | func (user *User) isValid() error { 19 | validEmail := govalidator.IsEmail(user.Email) 20 | if !validEmail { 21 | return errors.New("Invalid email!") 22 | } 23 | 24 | _, err := govalidator.ValidateStruct(user) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func NewUser(name string, userName string, email string) (*User, error) { 33 | user := User{ 34 | Name: name, 35 | UserName: userName, 36 | Email: email, 37 | } 38 | 39 | user.ID = uuid.NewV4().String() 40 | user.Books = []Book{} 41 | user.CreatedAt = time.Now() 42 | user.UpdatedAt = time.Now() 43 | 44 | err := user.isValid() 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | return &user, nil 50 | } 51 | -------------------------------------------------------------------------------- /tests/application/services/remove_account_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/services" 7 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | const fakeAccountId string = "any_valid_account_id" 12 | 13 | func MakeSut() (*services.RemoveAccountService, *mocks_test.FakeAccountRepository) { 14 | repo := mocks_test.NewFakeAccountRepository() 15 | repo.DeleteAccountByIdOutput = true 16 | sut := services.NewRemoveAccountService(repo) 17 | return sut, repo 18 | } 19 | 20 | func TestRemoveAccount_WithCorectID(t *testing.T) { 21 | sut, _ := MakeSut() 22 | message, err := sut.RemoveAccount(fakeAccountId) 23 | 24 | require.Nil(t, err) 25 | require.Equal(t, message, "account deleted") 26 | } 27 | 28 | func TestRemoveAccount_WithIncorrectID(t *testing.T) { 29 | sut, repo := MakeSut() 30 | repo.DeleteAccountByIdOutput = false 31 | 32 | message, err := sut.RemoveAccount(fakeAccountId) 33 | 34 | require.Error(t, err) 35 | require.Equal(t, message, "") 36 | require.Equal(t, err.Error(), "there is no account with this id") 37 | } 38 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/postgres-repository/helper.go: -------------------------------------------------------------------------------- 1 | package postgresrepository 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/joho/godotenv" 10 | _ "github.com/lib/pq" 11 | ) 12 | 13 | func GetPostgresConnection() *sql.DB { 14 | err := godotenv.Load() 15 | 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | // get data from environment variables 21 | host := os.Getenv("POSTGRES_HOST") 22 | port := os.Getenv("POSTGRES_PORT") 23 | user := os.Getenv("POSTGRES_USER") 24 | password := os.Getenv("POSTGRES_PASSWORD") 25 | dbname := os.Getenv("POSTGRES_DB_NAME") 26 | 27 | if host == "" || port == "" || user == "" || password == "" || dbname == "" { 28 | log.Fatal("Some environment variables are missing!") 29 | } 30 | 31 | // connection string 32 | psqlconn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) 33 | 34 | // open database 35 | db, err := sql.Open("postgres", psqlconn) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | // check db 41 | err = db.Ping() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | fmt.Println("database connected!") 47 | 48 | return db 49 | } 50 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/in-memory-account.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 5 | ) 6 | 7 | var accounts []*entities.Account 8 | 9 | type InMemoryAccountRepository struct{} 10 | 11 | func (repo *InMemoryAccountRepository) CheckAccountByEmail(email string) bool { 12 | for _, account := range accounts { 13 | if account.Email == email { 14 | return true 15 | } 16 | } 17 | return false 18 | } 19 | 20 | func (repo *InMemoryAccountRepository) CheckAccountByUserName(userName string) bool { 21 | for _, account := range accounts { 22 | if account.UserName == userName { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | 29 | func (repo *InMemoryAccountRepository) Save(account *entities.Account) error { 30 | accounts = append(accounts, account) 31 | return nil 32 | } 33 | 34 | func (repo *InMemoryAccountRepository) DeleteAccountById(accountId string) bool { 35 | for index, account := range accounts { 36 | if account.ID == accountId { 37 | accounts = removeIndex(accounts, index) 38 | return true 39 | } 40 | } 41 | return false 42 | } 43 | 44 | func NewInmemoryAccountRepository() *InMemoryAccountRepository { 45 | return &InMemoryAccountRepository{} 46 | } 47 | -------------------------------------------------------------------------------- /src/domain/entities/book.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | "github.com/asaskevich/govalidator" 6 | uuid "github.com/satori/go.uuid" 7 | "time" 8 | ) 9 | 10 | type Book struct { 11 | Base 12 | Title string `json:"title" valid:"required"` 13 | Author string `json:"author" valid:"required"` 14 | Price float64 `json:"price" valid:"required"` 15 | Description string `json:"description" valid:"required"` 16 | UserID string `json:"userId" valid:"required"` 17 | } 18 | 19 | func (book *Book) isValid() error { 20 | if book.Price <= 0 { 21 | return errors.New("Price must be greater than 0!") 22 | } 23 | _, err := govalidator.ValidateStruct(book) 24 | if err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | func NewBook(title string, author string, description string, price float64, UserId string) (*Book, error) { 31 | book := Book{ 32 | UserID: UserId, 33 | Title: title, 34 | Author: author, 35 | Description: description, 36 | Price: price, 37 | } 38 | 39 | book.ID = uuid.NewV4().String() 40 | book.CreatedAt = time.Now() 41 | book.UpdatedAt = time.Now() 42 | 43 | err := book.isValid() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return &book, nil 49 | } 50 | -------------------------------------------------------------------------------- /src/application/services/add-user.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | ) 10 | 11 | type AddUserService struct { 12 | userRepository protocols.UserRepository 13 | } 14 | 15 | func (s *AddUserService) Add(input *domaindto.AddUserInputDTO) (*domaindto.AddUserOutputDTO, error) { 16 | newUser, err := entities.NewUser(input.Name, input.UserName, input.Email) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | emailTaken := s.userRepository.CheckByEmail(input.Email) 22 | if emailTaken { 23 | return nil, errors.New("email already taken!") 24 | } 25 | 26 | userNameTaken := s.userRepository.CheckByUserName(input.UserName) 27 | if userNameTaken { 28 | return nil, errors.New("UserName already taken!") 29 | } 30 | 31 | err = s.userRepository.Save(newUser) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | output := domaindto.NewAddUserOutputDTO(newUser.ID, newUser.Name, newUser.UserName, newUser.Email) 37 | return output, nil 38 | } 39 | 40 | func NewAddUserService(repo protocols.UserRepository) *AddUserService { 41 | return &AddUserService{ 42 | userRepository: repo, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/application/services/get_users_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/services" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func makeListUsersSut() (*services.ListUsersService, *mocks_test.UserRepositorySpy) { 14 | repo := mocks_test.NewUserRepositorySpy() 15 | repo.ListUsersOutput = []*entities.User{} 16 | sut := services.NewListUsersService(repo) 17 | return sut, repo 18 | } 19 | 20 | func TestListUsersUseCase_WhenRepositoryReturnsBlankArray(t *testing.T) { 21 | sut, _ := makeListUsersSut() 22 | users := sut.List() 23 | 24 | require.Equal(t, []*domaindto.ListUsersDTO{}, users) 25 | } 26 | 27 | func TestListUsersUseCase_WhenRepositoryReturnsFilledArray(t *testing.T) { 28 | sut, repo := makeListUsersSut() 29 | fakeUser, _ := entities.NewUser("any_name", "any_username", "any_valid_email@gmail.com") 30 | repo.ListUsersOutput = []*entities.User{fakeUser} 31 | fakeList := domaindto.MapListUsersDTO([]*entities.User{fakeUser}) 32 | 33 | users := sut.List() 34 | 35 | require.ElementsMatch(t, fakeList, users) 36 | } 37 | -------------------------------------------------------------------------------- /src/presentation/controllers/get-user-by-id.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | 6 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 7 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 10 | ) 11 | 12 | type GetUserByIdController struct { 13 | service usecases.GetUserByIdUseCase 14 | } 15 | 16 | func NewGetUserByIdController(s usecases.GetUserByIdUseCase) *GetUserByIdController { 17 | return &GetUserByIdController{ 18 | service: s, 19 | } 20 | } 21 | 22 | func (controller *GetUserByIdController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 23 | if len(request.Params) == 0 { 24 | err := errors.New("Blank userId!") 25 | return helpers.BadRequest(err) 26 | } 27 | 28 | foundUser, err := controller.service.GetUserById(string(request.Params)) 29 | if err != nil { 30 | return helpers.ServerError(err) 31 | } 32 | 33 | if foundUser == nil { 34 | return helpers.NotFound(errors.New("user not found")) 35 | } 36 | 37 | outputDto := domaindto.NewGetUserByIdUseCaseOutputDTO( 38 | foundUser.ID, 39 | foundUser.Name, 40 | foundUser.Email, 41 | foundUser.UserName, 42 | foundUser.Books) 43 | 44 | return helpers.Ok(outputDto) 45 | } 46 | -------------------------------------------------------------------------------- /src/presentation/controllers/add-user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 7 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 10 | ) 11 | 12 | type AddUserController struct { 13 | service usecases.AddUserUseCase 14 | validator protocols.Validator 15 | } 16 | 17 | func (c *AddUserController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 18 | var inputUser domaindto.AddUserInputDTO 19 | err := json.Unmarshal(request.Body, &inputUser) 20 | if err != nil { 21 | return helpers.ServerError(err) 22 | } 23 | 24 | err = c.validator.Validate(&inputUser) 25 | if err != nil { 26 | return helpers.BadRequest(err) 27 | } 28 | 29 | newUser, err := c.service.Add(&inputUser) 30 | if err != nil { 31 | return helpers.BadRequest(err) 32 | } 33 | 34 | output := domaindto.NewAddUserOutputDTO(newUser.ID, newUser.Name, newUser.UserName, newUser.Email) 35 | return helpers.Ok(output) 36 | } 37 | 38 | func NewAddUserController(service usecases.AddUserUseCase, validator protocols.Validator) *AddUserController { 39 | return &AddUserController{ 40 | service: service, 41 | validator: validator, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/presentation/controllers/add-account.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 6 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 7 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 9 | ) 10 | 11 | type AddAccountController struct { 12 | useCase usecases.AddAccountUseCase 13 | validator protocols.Validator 14 | } 15 | 16 | func NewAddAccountController(useCase usecases.AddAccountUseCase, validator protocols.Validator) *AddAccountController { 17 | return &AddAccountController{ 18 | useCase: useCase, 19 | validator: validator, 20 | } 21 | } 22 | 23 | type AddAccountRequest struct { 24 | Body *domaindto.AddAccountInputDTO 25 | } 26 | 27 | func (c *AddAccountController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 28 | var accountInput domaindto.AddAccountInputDTO 29 | 30 | err := json.Unmarshal(request.Body, &accountInput) 31 | if err != nil { 32 | return helpers.ServerError(err) 33 | } 34 | 35 | err = c.validator.Validate(&accountInput) 36 | if err != nil { 37 | return helpers.BadRequest(err) 38 | } 39 | 40 | output, err := c.useCase.AddAccount(&accountInput) 41 | if err != nil { 42 | return helpers.BadRequest(err) 43 | } 44 | return helpers.Ok(output) 45 | } 46 | -------------------------------------------------------------------------------- /src/main/config/routes.go: -------------------------------------------------------------------------------- 1 | package configuration 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/main/adapters" 5 | "github.com/KPMGE/go-users-clean-api/src/main/factories" 6 | "github.com/gofiber/fiber/v2" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | func SetupRoutes(app *fiber.App, db *gorm.DB) *fiber.Router { 11 | api := app.Group("/api") 12 | 13 | api.Get("/accounts", adapters.FiberRouteAdapter(factories.MakeListAccountsController(db))) 14 | api.Post("/accounts", adapters.FiberRouteAdapter(factories.MakeAddAccountController(db))) 15 | api.Delete("/accounts/:accountId", adapters.FiberRouteAdapter(factories.MakeRemoveAccountController(db))) 16 | 17 | api.Post("/users", adapters.FiberRouteAdapter(factories.MakeAddUserController(db))) 18 | api.Get("/users/:userId", adapters.FiberRouteAdapter(factories.MakeGetUserByIdController(db))) 19 | api.Get("/users", adapters.FiberRouteAdapter(factories.MakeListUsersController(db))) 20 | api.Delete("/users/:userId", adapters.FiberRouteAdapter(factories.MakeDeleteUserController(db))) 21 | 22 | api.Get("/books", adapters.FiberRouteAdapter(factories.MakeListBooksController(db))) 23 | api.Get("/books/:bookId", adapters.FiberRouteAdapter(factories.MakeGetBookByIdController(db))) 24 | api.Post("/books", adapters.FiberRouteAdapter(factories.MakeAddBookController(db))) 25 | api.Delete("/books/:bookId", adapters.FiberRouteAdapter(factories.MakeRemoveBookController(db))) 26 | 27 | return &api 28 | } 29 | -------------------------------------------------------------------------------- /src/presentation/controllers/add-book.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/helpers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | ) 12 | 13 | type AddBookController struct { 14 | service usecases.AddBookUseCase 15 | validator protocols.Validator 16 | } 17 | 18 | func (c *AddBookController) Handle(request *protocols.HttpRequest) *protocols.HttpResponse { 19 | if request == nil { 20 | newError := errors.New("Invalid body!") 21 | return helpers.ServerError(newError) 22 | } 23 | 24 | var inputDto domaindto.AddBookUseCaseInputDTO 25 | err := json.Unmarshal(request.Body, &inputDto) 26 | if err != nil { 27 | newError := errors.New("Invalid body!") 28 | return helpers.ServerError(newError) 29 | } 30 | 31 | err = c.validator.Validate(&inputDto) 32 | if err != nil { 33 | return helpers.BadRequest(err) 34 | } 35 | 36 | output, err := c.service.AddBook(&inputDto) 37 | if err != nil { 38 | return helpers.BadRequest(err) 39 | } 40 | 41 | return helpers.Ok(output) 42 | } 43 | 44 | func NewAddBookController(service usecases.AddBookUseCase, validator protocols.Validator) *AddBookController { 45 | return &AddBookController{ 46 | service: service, 47 | validator: validator, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/in-memory-book.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 7 | ) 8 | 9 | var books []*entities.Book 10 | 11 | type InMemoryBookRepository struct{} 12 | 13 | func removeIndex[T any](s []T, index int) []T { 14 | return append(s[:index], s[index+1:]...) 15 | } 16 | 17 | func (repo *InMemoryBookRepository) List() ([]*entities.Book, error) { 18 | return books, nil 19 | } 20 | 21 | func (repo *InMemoryBookRepository) Add(newBook *entities.Book) (*entities.Book, error) { 22 | books = append(books, newBook) 23 | return newBook, nil 24 | } 25 | 26 | func (repo *InMemoryBookRepository) Find(bookId string) (*entities.Book, error) { 27 | for _, book := range books { 28 | if book.ID == bookId { 29 | return book, nil 30 | } 31 | } 32 | return nil, errors.New("book not found!") 33 | } 34 | 35 | func (repo *InMemoryBookRepository) Remove(bookId string) error { 36 | for index, book := range books { 37 | if book.ID == book.ID { 38 | books = removeIndex(books, index) 39 | return nil 40 | } 41 | } 42 | return errors.New("Cannot find book!") 43 | } 44 | 45 | func (repo *InMemoryBookRepository) Get(bookId string) (*entities.Book, error) { 46 | for _, book := range books { 47 | if book.ID == bookId { 48 | return book, nil 49 | } 50 | } 51 | return nil, errors.New("book not found!") 52 | } 53 | 54 | func NewInMemoryBookRepository() *InMemoryBookRepository { 55 | return &InMemoryBookRepository{} 56 | } 57 | -------------------------------------------------------------------------------- /tests/domain/entities/user_test.go: -------------------------------------------------------------------------------- 1 | package entities_test 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | const fakeName string = "any_name" 10 | const fakeUserName string = "any_user_name" 11 | const fakeEmail string = "any_valid_email@gmail.com" 12 | 13 | func TestUser_New(t *testing.T) { 14 | newUser, err := entities.NewUser(fakeName, fakeUserName, fakeEmail) 15 | 16 | require.Nil(t, err) 17 | require.Equal(t, []entities.Book{}, newUser.Books) 18 | require.NotNil(t, newUser) 19 | require.NotNil(t, newUser.ID) 20 | require.NotNil(t, newUser.UpdatedAt) 21 | require.NotNil(t, newUser.CreatedAt) 22 | require.Equal(t, fakeName, newUser.Name) 23 | require.Equal(t, fakeUserName, newUser.UserName) 24 | require.Equal(t, fakeEmail, newUser.Email) 25 | } 26 | 27 | func TestUser_New_WithInvalidEmail(t *testing.T) { 28 | newUser, err := entities.NewUser(fakeName, fakeUserName, "any_invalid_email") 29 | 30 | require.Error(t, err) 31 | require.Nil(t, newUser) 32 | require.Equal(t, err.Error(), "Invalid email!") 33 | } 34 | 35 | func TestUser_New_WithNullFields(t *testing.T) { 36 | newUser, err := entities.NewUser("", fakeUserName, fakeEmail) 37 | require.Error(t, err) 38 | require.Nil(t, newUser) 39 | 40 | newUser, err = entities.NewUser(fakeName, "", fakeEmail) 41 | require.Error(t, err) 42 | require.Nil(t, newUser) 43 | 44 | newUser, err = entities.NewUser(fakeName, fakeUserName, "") 45 | require.Error(t, err) 46 | require.Nil(t, newUser) 47 | } 48 | -------------------------------------------------------------------------------- /src/application/services/add-book.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | ) 10 | 11 | type AddBookService struct { 12 | bookRepo protocols.AddBookRepository 13 | userRepo protocols.UserRepository 14 | } 15 | 16 | func NewAddBookService(bookRepo protocols.AddBookRepository, userRepo protocols.UserRepository) *AddBookService { 17 | return &AddBookService{ 18 | bookRepo: bookRepo, 19 | userRepo: userRepo, 20 | } 21 | } 22 | 23 | func (useCase *AddBookService) AddBook(input *domaindto.AddBookUseCaseInputDTO) (*domaindto.AddBookUseCaseOutputDTO, error) { 24 | foundUser, err := useCase.userRepo.GetById(input.UserId) 25 | 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | if foundUser == nil { 31 | return nil, errors.New("User not found!") 32 | } 33 | 34 | newBook, err := entities.NewBook(input.Title, input.Author, input.Description, input.Price, foundUser.ID) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | _, err = useCase.bookRepo.Add(newBook) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | foundUser.Books = append(foundUser.Books, *newBook) 45 | 46 | outputDto := domaindto.AddBookUseCaseOutputDTO{ 47 | ID: newBook.ID, 48 | Title: newBook.Title, 49 | Author: newBook.Author, 50 | Price: newBook.Price, 51 | Description: newBook.Description, 52 | } 53 | 54 | return &outputDto, nil 55 | } 56 | -------------------------------------------------------------------------------- /tests/presentation/controllers/remove_account_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | type RemoveAccountServiceMock struct { 14 | Output string 15 | Error error 16 | } 17 | 18 | func (r *RemoveAccountServiceMock) RemoveAccount(accountId string) (string, error) { 19 | return r.Output, r.Error 20 | } 21 | 22 | func MakeSut() (*controllers.RemoveAccountController, *RemoveAccountServiceMock) { 23 | serviceMock := &RemoveAccountServiceMock{Output: "account deleted", Error: nil} 24 | sut := controllers.NewRemoveAccountController(serviceMock) 25 | return sut, serviceMock 26 | } 27 | 28 | func TestRemoveAccountController_ShouldReturnRightDataOnSuccess(t *testing.T) { 29 | sut, serviceMock := MakeSut() 30 | 31 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 32 | 33 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 34 | require.Equal(t, serviceMock.Output, httpResponse.Body) 35 | } 36 | 37 | func TestRemoveAccountController_ShouldReturnBadRequestIfServiceReturnsError(t *testing.T) { 38 | sut, serviceMock := MakeSut() 39 | serviceMock.Error = errors.New("there is not account with this id") 40 | 41 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 42 | 43 | require.Equal(t, http.StatusBadRequest, httpResponse.StatusCode) 44 | require.Equal(t, serviceMock.Error.Error(), httpResponse.Body) 45 | } 46 | -------------------------------------------------------------------------------- /tests/presentation/controllers/remove_user_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | type DeleteUserServiceMock struct { 14 | Output string 15 | Error error 16 | } 17 | 18 | func (s *DeleteUserServiceMock) DeleteUser(userId string) (string, error) { 19 | return s.Output, s.Error 20 | } 21 | 22 | func MakeDeleteUserControllerSut() (*controllers.DeleteUserController, *DeleteUserServiceMock) { 23 | serviceMock := &DeleteUserServiceMock{Output: "user deleted successfully", Error: nil} 24 | sut := controllers.NewDeleteUserController(serviceMock) 25 | return sut, serviceMock 26 | } 27 | 28 | func TestRemoveUserController_ShouldReturnRightDataOnSuccess(t *testing.T) { 29 | sut, serviceMock := MakeDeleteUserControllerSut() 30 | 31 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 32 | 33 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 34 | require.Equal(t, serviceMock.Output, httpResponse.Body) 35 | } 36 | 37 | func TestRemoveUserController_ShouldReturnBadRequestIfServiceReturnsError(t *testing.T) { 38 | sut, serviceMock := MakeDeleteUserControllerSut() 39 | serviceMock.Error = errors.New("service error") 40 | 41 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 42 | 43 | require.Equal(t, http.StatusBadRequest, httpResponse.StatusCode) 44 | require.Equal(t, serviceMock.Error.Error(), httpResponse.Body) 45 | } 46 | -------------------------------------------------------------------------------- /tests/application/services/list_books_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 10 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func MakeListBooksSut() (usecases.ListBooksUseCase, *mocks_test.ListBooksRepositoryStub) { 15 | repo := mocks_test.NewListBooksRepositoryStub() 16 | fakeBook, _ := entities.NewBook("any_title", "any_author", "any_description", 100.5, "any_user_id") 17 | repo.Output = append(repo.Output, fakeBook) 18 | repo.OutputError = nil 19 | sut := services.NewListBookService(repo) 20 | return sut, repo 21 | } 22 | 23 | func TestListBooksUseCase_ShoulReturnRightDataFromRepository(t *testing.T) { 24 | sut, _ := MakeListBooksSut() 25 | 26 | books, _ := sut.ListBooks() 27 | 28 | require.Equal(t, 1, len(books)) 29 | require.Equal(t, "any_title", books[0].Title) 30 | require.Equal(t, "any_author", books[0].Author) 31 | require.Equal(t, "any_description", books[0].Description) 32 | require.Equal(t, "any_user_id", books[0].UserID) 33 | require.Equal(t, 100.5, books[0].Price) 34 | } 35 | 36 | func TestListBooksUseCase_ShoulReturnErrorIfRepositoryReturnsError(t *testing.T) { 37 | sut, repo := MakeListBooksSut() 38 | repo.OutputError = errors.New("repo error") 39 | 40 | books, err := sut.ListBooks() 41 | 42 | require.Error(t, err) 43 | require.Equal(t, repo.OutputError, err) 44 | require.Nil(t, books) 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/KPMGE/go-users-clean-api 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/andybalholm/brotli v1.0.4 // indirect 7 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/gofiber/fiber/v2 v2.31.0 // indirect 10 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 11 | github.com/jackc/pgconn v1.12.1 // indirect 12 | github.com/jackc/pgio v1.0.0 // indirect 13 | github.com/jackc/pgpassfile v1.0.0 // indirect 14 | github.com/jackc/pgproto3/v2 v2.3.0 // indirect 15 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect 16 | github.com/jackc/pgtype v1.11.0 // indirect 17 | github.com/jackc/pgx/v4 v4.16.1 // indirect 18 | github.com/jinzhu/inflection v1.0.0 // indirect 19 | github.com/jinzhu/now v1.1.5 // indirect 20 | github.com/joho/godotenv v1.4.0 // indirect 21 | github.com/klauspost/compress v1.15.0 // indirect 22 | github.com/lib/pq v1.10.6 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/satori/go.uuid v1.2.0 // indirect 25 | github.com/stretchr/testify v1.7.1 // indirect 26 | github.com/valyala/bytebufferpool v1.0.0 // indirect 27 | github.com/valyala/fasthttp v1.34.0 // indirect 28 | github.com/valyala/tcplisten v1.0.0 // indirect 29 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect 30 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect 31 | golang.org/x/text v0.3.7 // indirect 32 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 33 | gorm.io/driver/postgres v1.3.8 // indirect 34 | gorm.io/gorm v1.23.8 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /tests/domain/entities/account_test.go: -------------------------------------------------------------------------------- 1 | package entities_test 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | const ( 10 | fakeAccountUserName string = "any_name" 11 | fakeAccountEmail string = "any_valid_email@gmail.com" 12 | fakeAccountPassword string = "any_password" 13 | ) 14 | 15 | func TestNewAccount_WithRightData(t *testing.T) { 16 | account, err := entities.NewAccount(fakeAccountUserName, fakeAccountEmail, fakeAccountPassword) 17 | 18 | require.Nil(t, err) 19 | require.Equal(t, account.Email, fakeAccountEmail) 20 | require.Equal(t, account.UserName, fakeAccountUserName) 21 | require.Equal(t, account.Password, fakeAccountPassword) 22 | require.NotNil(t, account.ID, fakeAccountPassword) 23 | require.NotNil(t, account.CreatedAt, fakeAccountPassword) 24 | require.NotNil(t, account.UpdatedAt, fakeAccountPassword) 25 | } 26 | 27 | func TestNewAccount_WithInvalidEmail(t *testing.T) { 28 | account, err := entities.NewAccount(fakeAccountUserName, "any_invalid_email", fakeAccountPassword) 29 | 30 | require.Nil(t, account) 31 | require.Error(t, err) 32 | require.Equal(t, err.Error(), "Invalid email!") 33 | } 34 | 35 | func TestNewAccount_WithBlankFields(t *testing.T) { 36 | account, err := entities.NewAccount("", fakeAccountEmail, fakeAccountPassword) 37 | require.Nil(t, account) 38 | require.Error(t, err) 39 | 40 | account, err = entities.NewAccount(fakeAccountUserName, "", fakeAccountPassword) 41 | require.Nil(t, account) 42 | require.Error(t, err) 43 | 44 | account, err = entities.NewAccount(fakeAccountUserName, fakeAccountEmail, "") 45 | require.Nil(t, account) 46 | require.Error(t, err) 47 | } 48 | -------------------------------------------------------------------------------- /tests/presentation/controllers/remove_book_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type RemoveBookServiceMock struct { 15 | Output *domaindto.RemoveBookUseCaseOutputDTO 16 | Error error 17 | } 18 | 19 | func (s *RemoveBookServiceMock) RemoveBook(bookId string) (*domaindto.RemoveBookUseCaseOutputDTO, error) { 20 | return s.Output, s.Error 21 | } 22 | 23 | func MakeRemoveBookControllerSut() (*controllers.RemoveBookController, *RemoveBookServiceMock) { 24 | serviceMock := &RemoveBookServiceMock{} 25 | sut := controllers.NewRemoveBookController(serviceMock) 26 | 27 | return sut, serviceMock 28 | } 29 | 30 | func TestRemoveBookController_ShouldReturnRightDataOnSuccess(t *testing.T) { 31 | sut, serviceMock := MakeRemoveBookControllerSut() 32 | 33 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 34 | 35 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 36 | require.Equal(t, serviceMock.Output, httpResponse.Body) 37 | } 38 | 39 | func TestRemoveBookController_ShouldReturnBadRequestIfServiceReturnsError(t *testing.T) { 40 | sut, serviceMock := MakeRemoveBookControllerSut() 41 | serviceMock.Error = errors.New("service error") 42 | 43 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 44 | 45 | require.Equal(t, http.StatusBadRequest, httpResponse.StatusCode) 46 | require.Equal(t, serviceMock.Error.Error(), httpResponse.Body) 47 | } 48 | -------------------------------------------------------------------------------- /src/application/services/add-account.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | ) 10 | 11 | type AddAccountService struct { 12 | accountRepository protocols.AccountRepository 13 | hasher protocols.Hasher 14 | } 15 | 16 | func (useCase *AddAccountService) AddAccount(input *domaindto.AddAccountInputDTO) (*domaindto.AddAccountOutputDTO, error) { 17 | emailTaken := useCase.accountRepository.CheckAccountByEmail(input.Email) 18 | if emailTaken { 19 | return nil, errors.New("email already taken") 20 | } 21 | 22 | userNameTaken := useCase.accountRepository.CheckAccountByUserName(input.UserName) 23 | if userNameTaken { 24 | return nil, errors.New("username already taken") 25 | } 26 | 27 | if input.Password != input.ConfirmPassword { 28 | return nil, errors.New("password and confirmPassword must match") 29 | } 30 | 31 | hashedPassword := useCase.hasher.Hash(input.Password) 32 | account, err := entities.NewAccount(input.UserName, input.Email, hashedPassword) 33 | 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | err = useCase.accountRepository.Save(account) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | output := domaindto.NewAddAccountOutputDTO(account.ID, account.UserName, account.Email) 44 | 45 | return output, nil 46 | } 47 | 48 | func NewAddAccountService(accountRepository protocols.AccountRepository, hasher protocols.Hasher) *AddAccountService { 49 | return &AddAccountService{ 50 | accountRepository: accountRepository, 51 | hasher: hasher, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/in-memory-users.go: -------------------------------------------------------------------------------- 1 | package repositories 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 7 | ) 8 | 9 | var users []*entities.User 10 | 11 | type InMemoryUserRepository struct{} 12 | 13 | func (repo *InMemoryUserRepository) Save(user *entities.User) error { 14 | users = append(users, user) 15 | return nil 16 | } 17 | 18 | func (repo *InMemoryUserRepository) CheckByEmail(email string) bool { 19 | for _, user := range users { 20 | if user.Email == email { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | 27 | func (repo *InMemoryUserRepository) CheckByUserName(userName string) bool { 28 | for _, user := range users { 29 | if user.UserName == userName { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func (repo *InMemoryUserRepository) List() []*entities.User { 37 | return users 38 | } 39 | 40 | func (repo *InMemoryUserRepository) Delete(userId string) error { 41 | for index, user := range users { 42 | if user.ID == userId { 43 | users = removeIndex(users, index) 44 | return nil 45 | } 46 | } 47 | return errors.New("No user with provided id") 48 | } 49 | 50 | func (repo *InMemoryUserRepository) CheckById(userId string) bool { 51 | for _, user := range users { 52 | if user.ID == userId { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | func (repo *InMemoryUserRepository) GetById(userId string) (*entities.User, error) { 60 | for _, user := range users { 61 | if user.ID == userId { 62 | return user, nil 63 | } 64 | } 65 | return nil, errors.New("User not found") 66 | } 67 | 68 | func NewInMemoryUserRepository() *InMemoryUserRepository { 69 | return &InMemoryUserRepository{} 70 | } 71 | -------------------------------------------------------------------------------- /tests/application/mocks/user-repository.go: -------------------------------------------------------------------------------- 1 | package mocks_test 2 | 3 | import "github.com/KPMGE/go-users-clean-api/src/domain/entities" 4 | 5 | type UserRepositorySpy struct { 6 | AddInput *entities.User 7 | AddOutput error 8 | CheckByEmailInput string 9 | CheckByEmailOutput bool 10 | CheckByUserNameInput string 11 | CheckByUserNameOutput bool 12 | ListUsersOutput []*entities.User 13 | DeleteInput string 14 | DeleteOutput error 15 | CheckByIdInput string 16 | CheckByIdOuput bool 17 | GetByidInput string 18 | GetByidOutput *entities.User 19 | GetByidError error 20 | } 21 | 22 | func (repo *UserRepositorySpy) Save(user *entities.User) error { 23 | repo.AddInput = user 24 | return repo.AddOutput 25 | } 26 | 27 | func (repo *UserRepositorySpy) CheckByEmail(email string) bool { 28 | repo.CheckByEmailInput = email 29 | return repo.CheckByEmailOutput 30 | } 31 | 32 | func (repo *UserRepositorySpy) CheckByUserName(email string) bool { 33 | repo.CheckByUserNameInput = email 34 | return repo.CheckByUserNameOutput 35 | } 36 | 37 | func (repo *UserRepositorySpy) List() []*entities.User { 38 | return repo.ListUsersOutput 39 | } 40 | 41 | func (repo *UserRepositorySpy) Delete(userId string) error { 42 | repo.DeleteInput = userId 43 | return repo.DeleteOutput 44 | } 45 | 46 | func (repo *UserRepositorySpy) CheckById(userId string) bool { 47 | repo.CheckByIdInput = userId 48 | return repo.CheckByIdOuput 49 | } 50 | 51 | func (repo *UserRepositorySpy) GetById(userId string) (*entities.User, error) { 52 | repo.GetByidInput = userId 53 | return repo.GetByidOutput, repo.GetByidError 54 | } 55 | 56 | func NewUserRepositorySpy() *UserRepositorySpy { 57 | return &UserRepositorySpy{} 58 | } 59 | -------------------------------------------------------------------------------- /tests/presentation/controllers/list_books_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func MakeListBoooksControllerSut() (*controllers.ListBooksController, *mocks_test.ListBooksRepositoryStub) { 16 | repo := mocks_test.NewListBooksRepositoryStub() 17 | fakeBook, _ := entities.NewBook("any_title", "any_author", "any_description", 200.2, "any_user_id") 18 | repo.Output = append(repo.Output, fakeBook) 19 | repo.OutputError = nil 20 | service := services.NewListBookService(repo) 21 | sut := controllers.NewListBooksController(service) 22 | return sut, repo 23 | } 24 | 25 | func TestListBooksController_shouldReturnRightDataOnSuccess(t *testing.T) { 26 | sut, serviceStub := MakeListBoooksControllerSut() 27 | 28 | fakeRequest := protocols.NewHttpRequest(nil, nil) 29 | httpResponse := sut.Handle(fakeRequest) 30 | 31 | require.Equal(t, 200, httpResponse.StatusCode) 32 | require.Equal(t, serviceStub.Output, httpResponse.Body) 33 | } 34 | 35 | func TestListBooksController_shouldServerErrorIfRepositoryReturnsError(t *testing.T) { 36 | sut, repo := MakeListBoooksControllerSut() 37 | repo.OutputError = errors.New("repo error") 38 | 39 | fakeRequest := protocols.NewHttpRequest(nil, nil) 40 | httpResponse := sut.Handle(fakeRequest) 41 | 42 | require.Equal(t, 500, httpResponse.StatusCode) 43 | require.Equal(t, repo.OutputError.Error(), httpResponse.Body) 44 | } 45 | -------------------------------------------------------------------------------- /src/application/services/remove-book.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/KPMGE/go-users-clean-api/src/application/protocols" 7 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | ) 10 | 11 | type RemoveBookService struct { 12 | removeBookRepo protocols.RemoveBookRepository 13 | findBookRepo protocols.FindBookRepository 14 | userRepo protocols.UserRepository 15 | } 16 | 17 | func NewRemoveBookService( 18 | removeBookRepo protocols.RemoveBookRepository, 19 | findBookRepo protocols.FindBookRepository, 20 | userRepo protocols.UserRepository, 21 | ) *RemoveBookService { 22 | return &RemoveBookService{ 23 | userRepo: userRepo, 24 | removeBookRepo: removeBookRepo, 25 | findBookRepo: findBookRepo, 26 | } 27 | } 28 | 29 | func removeIndex[T any](s []T, index int) []T { 30 | return append(s[:index], s[index+1:]...) 31 | } 32 | 33 | func getBookIndex(books []entities.Book, bookId string) int { 34 | for i, book := range books { 35 | if book.ID == bookId { 36 | return i 37 | } 38 | } 39 | return -1 40 | } 41 | 42 | func (s *RemoveBookService) RemoveBook(bookId string) (*domaindto.RemoveBookUseCaseOutputDTO, error) { 43 | foundBook, err := s.findBookRepo.Find(bookId) 44 | if err != nil { 45 | return nil, err 46 | } 47 | if foundBook == nil { 48 | return nil, errors.New("book not found!") 49 | } 50 | 51 | err = s.removeBookRepo.Remove(bookId) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | outputDto := domaindto.RemoveBookUseCaseOutputDTO{ 57 | Title: foundBook.Title, 58 | Author: foundBook.Author, 59 | Description: foundBook.Description, 60 | Price: foundBook.Price, 61 | UserId: foundBook.UserID, 62 | } 63 | 64 | return &outputDto, nil 65 | } 66 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/postgres-repository/postgres-books.go: -------------------------------------------------------------------------------- 1 | package postgresrepository 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type PostgresBookRepository struct { 12 | db *gorm.DB 13 | } 14 | 15 | func (repo *PostgresBookRepository) List() ([]*entities.Book, error) { 16 | var books []*entities.Book 17 | 18 | result := repo.db.Find(&books) 19 | 20 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 21 | return []*entities.Book{}, nil 22 | } 23 | 24 | CheckError(result.Error) 25 | 26 | return books, nil 27 | } 28 | 29 | func (repo *PostgresBookRepository) Add(newBook *entities.Book) (*entities.Book, error) { 30 | result := repo.db.Create(newBook) 31 | if result.Error != nil { 32 | return nil, result.Error 33 | } 34 | return newBook, nil 35 | } 36 | 37 | func (repo *PostgresBookRepository) Find(bookId string) (*entities.Book, error) { 38 | var foundBook entities.Book 39 | 40 | result := repo.db.First(&foundBook, fmt.Sprintf("id = '%s'", bookId)) 41 | 42 | if result.Error != nil { 43 | return nil, result.Error 44 | } 45 | 46 | return &foundBook, nil 47 | } 48 | 49 | func (repo *PostgresBookRepository) Get(bookId string) (*entities.Book, error) { 50 | var foundBook entities.Book 51 | 52 | result := repo.db.First(&foundBook, fmt.Sprintf("id = '%s'", bookId)) 53 | 54 | if result.Error != nil { 55 | return nil, result.Error 56 | } 57 | 58 | return &foundBook, nil 59 | } 60 | 61 | func (repo *PostgresBookRepository) Remove(bookId string) error { 62 | result := repo.db.Delete(&entities.Book{}, fmt.Sprintf("id = '%s'", bookId)) 63 | return result.Error 64 | } 65 | 66 | func NewPostgresBookRepository(db *gorm.DB) *PostgresBookRepository { 67 | return &PostgresBookRepository{ 68 | db: db, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/postgres-repository/postgres-account.go: -------------------------------------------------------------------------------- 1 | package postgresrepository 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | func CheckError(err error) { 13 | if err != nil { 14 | log.Fatalln(err) 15 | } 16 | } 17 | 18 | type PostgresAccountRepository struct { 19 | db *gorm.DB 20 | } 21 | 22 | func (repo *PostgresAccountRepository) CheckAccountByEmail(email string) bool { 23 | var account entities.Account 24 | 25 | result := repo.db.First(&account, fmt.Sprintf("email='%s'", email)) 26 | 27 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 28 | return false 29 | } 30 | 31 | CheckError(result.Error) 32 | 33 | return true 34 | } 35 | 36 | func (repo *PostgresAccountRepository) CheckAccountByUserName(userName string) bool { 37 | var account entities.Account 38 | 39 | result := repo.db.First(&account, fmt.Sprintf("user_name = '%s'", userName)) 40 | 41 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 42 | return false 43 | } 44 | 45 | CheckError(result.Error) 46 | 47 | return true 48 | } 49 | 50 | func (repo *PostgresAccountRepository) Save(account *entities.Account) error { 51 | result := repo.db.Create(account) 52 | CheckError(result.Error) 53 | return nil 54 | } 55 | 56 | func (repo *PostgresAccountRepository) DeleteAccountById(accountId string) bool { 57 | result := repo.db.Delete(&entities.Account{}, fmt.Sprintf("id = '%s'", accountId)) 58 | CheckError(result.Error) 59 | if result.RowsAffected < 1 { 60 | return false 61 | } 62 | return true 63 | } 64 | 65 | func (repo *PostgresAccountRepository) ListAccounts() []entities.Account { 66 | var accounts []entities.Account 67 | 68 | result := repo.db.Find(&accounts) 69 | CheckError(result.Error) 70 | 71 | return accounts 72 | } 73 | 74 | func NewPostgresAccountRepository(db *gorm.DB) *PostgresAccountRepository { 75 | return &PostgresAccountRepository{ 76 | db: db, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | # Api Documentation 2 | 3 | 4 | ### Environment variables 5 | You are supposed to set up a __.env__ file at the root of this project. That file's gonna be used 6 | to configure your database among other stuff. Here it is an example of such a file. Don't forget to change 7 | those default configurations. 8 | 9 | ```bash 10 | POSTGRES_HOST="localhost" 11 | POSTGRES_PASSWORD="root" 12 | POSTGRES_PORT=5432 13 | POSTGRES_USER="postgres" 14 | POSTGRES_PASSWORD="root" 15 | POSTGRES_DB_NAME="users" 16 | ``` 17 | 18 | --- 19 | 20 | ### Postgres database 21 | I've made a __docker-compose__ file for creating a simple postgres databse container for you. To use it, 22 | just make sure you've got docker and docker-compose installed on your machine. Then run the simple command: 23 | 24 | ```bash 25 | sudo docker-compose up -d 26 | ``` 27 | 28 | --- 29 | 30 | ### Tables on the database 31 | In this project, i have used the *gorm* framework to deal with the migrations. So, you don't need to 32 | worry about it, it will create the tables automatically when you run the program. 33 | 34 | After a few seconds, your container should be up and running. The default database is __users__ and 35 | the default password, root. You can change them to whatever you want later! 36 | 37 | ### How do i run it? 38 | In order to run this project, make sure you have got golang properly installed on your machine, then run 39 | the command at the root of the project 40 | 41 | ```bash 42 | go run ./src/main/main.go 43 | ``` 44 | 45 | --- 46 | 47 | ### How to generate api route docs. 48 | You can easily generate your api route docs. First of all, you're supposed to run the command below, make sure you've got 49 | npx on your machine. 50 | 51 | ```bash 52 | npx insomnia-documenter --config ./golang-api.json 53 | ``` 54 | 55 | Then, you can run a server with your brand new doc by using: 56 | ```bash 57 | npx serve 58 | ``` 59 | 60 | Now it's as simple as opening a new tab on your favourite browser and access the link: 61 | > http://localhost:3000 62 | -------------------------------------------------------------------------------- /tests/application/services/remove_user_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | const FAKE_USER_ID string = "any_user_id" 13 | 14 | func MakeDeleteUserSut() (*services.DeleteUserService, *mocks_test.UserRepositorySpy) { 15 | repo := mocks_test.NewUserRepositorySpy() 16 | repo.DeleteOutput = nil 17 | repo.CheckByIdOuput = true 18 | sut := services.NewDeleteUserService(repo) 19 | return sut, repo 20 | } 21 | 22 | func TestDeleteUserUseCase_ShouldReturnRightDataOnSuccess(t *testing.T) { 23 | sut, _ := MakeDeleteUserSut() 24 | message, err := sut.DeleteUser(FAKE_USER_ID) 25 | 26 | require.Nil(t, err) 27 | require.Equal(t, "user deleted successfully", message) 28 | } 29 | 30 | func TestDeleteUserUseCase_ShouldCallUserChechUserByIdRepositoryWithRightId(t *testing.T) { 31 | sut, repo := MakeDeleteUserSut() 32 | sut.DeleteUser(FAKE_USER_ID) 33 | require.Equal(t, FAKE_USER_ID, repo.CheckByIdInput) 34 | } 35 | 36 | func TestDeleteUserUseCase_ShouldCallDelteUserRepositoryWithRightId(t *testing.T) { 37 | sut, repo := MakeDeleteUserSut() 38 | sut.DeleteUser(FAKE_USER_ID) 39 | require.Equal(t, FAKE_USER_ID, repo.DeleteInput) 40 | } 41 | 42 | func TestDeleteUserUseCase_ShouldReturnErrorIfWrongIdIsProvided(t *testing.T) { 43 | sut, repo := MakeDeleteUserSut() 44 | repo.CheckByIdOuput = false 45 | 46 | message, err := sut.DeleteUser("any_wrong_id") 47 | 48 | require.Error(t, err) 49 | require.Equal(t, "there is no user with the provided id", err.Error()) 50 | require.Equal(t, "", message) 51 | } 52 | 53 | func TestDeleteUserUseCase_ShouldReturnErrorIfDelteRepositoryReturnsError(t *testing.T) { 54 | sut, repo := MakeDeleteUserSut() 55 | repo.DeleteOutput = errors.New("Internal error") 56 | 57 | message, err := sut.DeleteUser(FAKE_USER_ID) 58 | 59 | require.Error(t, err) 60 | require.Equal(t, "", message) 61 | require.Equal(t, "Internal error", err.Error()) 62 | } 63 | -------------------------------------------------------------------------------- /tests/application/services/get_user_by_id_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/application/services" 9 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 10 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | const fakeName string = "any_user_name" 15 | 16 | func MakeGetUserByIdSut() (*services.GetUserByIdService, *mocks_test.UserRepositorySpy) { 17 | repo := mocks_test.NewUserRepositorySpy() 18 | fakeUser, err := entities.NewUser(fakeName, fakeUserName, fakeEmail) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | repo.GetByidOutput = fakeUser 23 | sut := services.NewGetUserByIdService(repo) 24 | return sut, repo 25 | } 26 | 27 | func TestGetUserByIdUseCase_ShouldCallRepositoryCorrectly(t *testing.T) { 28 | sut, repo := MakeGetUserByIdSut() 29 | 30 | sut.GetUserById(FAKE_USER_ID) 31 | 32 | require.Equal(t, FAKE_USER_ID, repo.GetByidInput) 33 | } 34 | 35 | func TestGetUserByIdUseCase_ShouldReturnFoundUser(t *testing.T) { 36 | sut, _ := MakeGetUserByIdSut() 37 | 38 | foundUser, _ := sut.GetUserById(FAKE_USER_ID) 39 | 40 | require.Equal(t, fakeName, foundUser.Name) 41 | require.Equal(t, fakeUserName, foundUser.UserName) 42 | require.Equal(t, fakeEmail, foundUser.Email) 43 | require.NotNil(t, foundUser.ID) 44 | } 45 | 46 | func TestGetUserByIdUseCase_ShouldReturnErrorIfRepositoryRetunsNil(t *testing.T) { 47 | sut, repo := MakeGetUserByIdSut() 48 | repo.GetByidOutput = nil 49 | 50 | foundUser, err := sut.GetUserById("invalid_id") 51 | 52 | require.Error(t, err) 53 | require.Equal(t, "User not found!", err.Error()) 54 | require.Nil(t, foundUser) 55 | } 56 | 57 | func TestGetUserByIdUseCase_ShouldReturnErrorIfRepositoryRetunsError(t *testing.T) { 58 | sut, repo := MakeGetUserByIdSut() 59 | repo.GetByidError = errors.New("repository error") 60 | 61 | foundUser, err := sut.GetUserById("invalid_id") 62 | 63 | require.Error(t, err) 64 | require.Equal(t, "repository error", err.Error()) 65 | require.Nil(t, foundUser) 66 | } 67 | -------------------------------------------------------------------------------- /tests/domain/entities/book_test.go: -------------------------------------------------------------------------------- 1 | package entities_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 8 | uuid "github.com/satori/go.uuid" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | const ( 13 | fakeTitle string = "any_title" 14 | fakeAuthor string = "any_author" 15 | fakeDescription string = "any_description" 16 | fakePrice float64 = 5.3 17 | ) 18 | 19 | func makeFakeUser() *entities.User { 20 | user := entities.User{ 21 | Name: "any_name", 22 | UserName: "any_user_name", 23 | Email: "any_valid_email@gmail.com", 24 | } 25 | 26 | user.ID = uuid.NewV4().String() 27 | user.CreatedAt = time.Now() 28 | user.UpdatedAt = time.Now() 29 | 30 | return &user 31 | } 32 | 33 | func TestNewBook_WithRighData(t *testing.T) { 34 | fakeUser := makeFakeUser() 35 | newBook, err := entities.NewBook(fakeTitle, fakeAuthor, fakeDescription, fakePrice, fakeUser.ID) 36 | 37 | require.Nil(t, err) 38 | require.NotEmpty(t, newBook.CreatedAt) 39 | require.NotEmpty(t, newBook.UpdatedAt) 40 | require.NotEmpty(t, newBook.ID) 41 | require.Equal(t, newBook.Title, fakeTitle) 42 | require.Equal(t, newBook.Author, fakeAuthor) 43 | require.Equal(t, newBook.Price, fakePrice) 44 | require.Equal(t, newBook.Description, fakeDescription) 45 | } 46 | 47 | func TestNewBook_WithPriceLessThanOrEqualTo0(t *testing.T) { 48 | fakeUser := makeFakeUser() 49 | newBook, err := entities.NewBook(fakeTitle, fakeAuthor, fakeDescription, 0, fakeUser.ID) 50 | 51 | require.Error(t, err) 52 | require.Nil(t, newBook) 53 | require.Equal(t, err.Error(), "Price must be greater than 0!") 54 | } 55 | 56 | func TestNewBook_WithNullFields(t *testing.T) { 57 | fakeUser := makeFakeUser() 58 | 59 | newBook, err := entities.NewBook("", fakeAuthor, fakeDescription, fakePrice, fakeUser.ID) 60 | require.Error(t, err) 61 | require.Nil(t, newBook) 62 | 63 | newBook, err = entities.NewBook(fakeTitle, "", fakeDescription, fakePrice, fakeUser.ID) 64 | require.Error(t, err) 65 | require.Nil(t, newBook) 66 | 67 | newBook, err = entities.NewBook(fakeTitle, fakeAuthor, "", fakePrice, fakeUser.ID) 68 | require.Error(t, err) 69 | require.Nil(t, newBook) 70 | } 71 | -------------------------------------------------------------------------------- /tests/application/services/get_book_by_id_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 10 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func MakeGetBookByIdSut() (usecases.GetBookByIdUseCase, *mocks_test.GetBookByIdRepositorySpy) { 15 | getBookRepo := mocks_test.NewGetBookByIdRepositorySpy() 16 | fakeBook, _ := entities.NewBook("any_title", "any_author", "any_description", 100.23, "any_user_id") 17 | getBookRepo.Output = fakeBook 18 | sut := services.NewGetBookByIdService(getBookRepo) 19 | return sut, getBookRepo 20 | } 21 | 22 | func TestGetBookByIdUseCase_ShouldCallRepositoryWithRightData(t *testing.T) { 23 | sut, getBookRepo := MakeGetBookByIdSut() 24 | sut.GetBookById("any_book_id") 25 | require.Equal(t, "any_book_id", getBookRepo.Input) 26 | } 27 | 28 | func TestGetBookByIdUseCase_ShouldRetunNilIfNoBookIsFound(t *testing.T) { 29 | sut, getBookRepo := MakeGetBookByIdSut() 30 | getBookRepo.Output = nil 31 | foundBook, _ := sut.GetBookById("any_book_id") 32 | require.Nil(t, foundBook) 33 | } 34 | 35 | func TestGetBookByIdUseCase_ShouldRetunRightBookOnSuccess(t *testing.T) { 36 | sut, _ := MakeGetBookByIdSut() 37 | foundBook, _ := sut.GetBookById("any_book_id") 38 | require.Equal(t, "any_title", foundBook.Title) 39 | require.Equal(t, "any_author", foundBook.Author) 40 | require.Equal(t, "any_user_id", foundBook.UserID) 41 | require.Equal(t, "any_description", foundBook.Description) 42 | require.Equal(t, 100.23, foundBook.Price) 43 | require.NotNil(t, foundBook.CreatedAt) 44 | require.NotNil(t, foundBook.UpdatedAt) 45 | require.NotNil(t, foundBook.ID) 46 | } 47 | 48 | func TestGetBookByIdUseCase_ShouldRetunErrorIfRepositoryReturnsError(t *testing.T) { 49 | sut, getBookRepo := MakeGetBookByIdSut() 50 | getBookRepo.OutputError = errors.New("repo error") 51 | foundBook, err := sut.GetBookById("any_book_id") 52 | require.Nil(t, foundBook) 53 | require.Error(t, err) 54 | require.Equal(t, "repo error", err.Error()) 55 | } 56 | -------------------------------------------------------------------------------- /tests/presentation/controllers/get_book_by_id_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | type GetBookByIdServiceMock struct { 15 | Output *entities.Book 16 | Error error 17 | } 18 | 19 | func (g *GetBookByIdServiceMock) GetBookById(bookId string) (*entities.Book, error) { 20 | return g.Output, g.Error 21 | } 22 | 23 | func MakeGetBookByIdControllerSut() (*controllers.GetBookByIdController, *GetBookByIdServiceMock) { 24 | fakeBook, _ := entities.NewBook("any_title", "any_author", "any_description", 203.43, "any_user_id") 25 | serviceMock := &GetBookByIdServiceMock{Output: fakeBook, Error: nil} 26 | sut := controllers.NewGetBookByIdController(serviceMock) 27 | return sut, serviceMock 28 | } 29 | 30 | func TestGetBookByIdController_ShouldReturnRightDataOnSuccess(t *testing.T) { 31 | sut, serviceMock := MakeGetBookByIdControllerSut() 32 | 33 | httpResponse := sut.Handle(protocols.NewHttpRequest([]byte("any_id"), nil)) 34 | 35 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 36 | require.Equal(t, serviceMock.Output, httpResponse.Body) 37 | } 38 | 39 | func TestGetBookByIdController_ShouldReturnNotFoundIfServiceReturnsNull(t *testing.T) { 40 | sut, serviceMock := MakeGetBookByIdControllerSut() 41 | serviceMock.Output = nil 42 | expectedError := errors.New("book not found") 43 | 44 | httpResponse := sut.Handle(protocols.NewHttpRequest([]byte("any_id"), nil)) 45 | 46 | require.Equal(t, http.StatusNotFound, httpResponse.StatusCode) 47 | require.Equal(t, expectedError.Error(), httpResponse.Body) 48 | } 49 | 50 | func TestGetBookByIdController_ShouldReturnServerErrorIfServiceReturnsError(t *testing.T) { 51 | sut, serviceMock := MakeGetBookByIdControllerSut() 52 | serviceMock.Error = errors.New("service error") 53 | 54 | httpResponse := sut.Handle(protocols.NewHttpRequest([]byte("any_id"), nil)) 55 | 56 | require.Equal(t, http.StatusInternalServerError, httpResponse.StatusCode) 57 | require.Equal(t, serviceMock.Error.Error(), httpResponse.Body) 58 | } 59 | -------------------------------------------------------------------------------- /tests/presentation/controllers/add_account_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/application/services" 9 | "github.com/KPMGE/go-users-clean-api/src/infrasctructure/repositories" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 11 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 12 | controllermocks_test "github.com/KPMGE/go-users-clean-api/tests/presentation/controller-mocks" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | type FakeHasher struct{} 17 | 18 | func (hasher *FakeHasher) Hash(plainText string) string { 19 | return "some_hash" 20 | } 21 | 22 | func NewFakeHasher() *FakeHasher { 23 | return &FakeHasher{} 24 | } 25 | 26 | const fakePassword string = "any_password" 27 | 28 | func makeSut() (*controllers.AddAccountController, *controllermocks_test.ValidatorMock) { 29 | repo := repositories.NewInmemoryAccountRepository() 30 | hasher := NewFakeHasher() 31 | validatorMock := &controllermocks_test.ValidatorMock{Output: nil} 32 | useCase := services.NewAddAccountService(repo, hasher) 33 | sut := controllers.NewAddAccountController(useCase, validatorMock) 34 | return sut, validatorMock 35 | } 36 | 37 | func TestAddAccountController_WithRightData(t *testing.T) { 38 | sut, _ := makeSut() 39 | 40 | fakeInput := `{ 41 | "userName": "any_username", 42 | "email": "any_email@gmail.com", 43 | "password": "any_password", 44 | "confirmPassword": "any_password" 45 | } 46 | ` 47 | 48 | fakeRequest := protocols.NewHttpRequest([]byte(fakeInput), nil) 49 | httpResponse := sut.Handle(fakeRequest) 50 | 51 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 52 | require.NotNil(t, httpResponse.Body) 53 | } 54 | 55 | func TestAddAccountController_ShouldReturnBadRequestValidatorReturnsError(t *testing.T) { 56 | sut, validatorMock := makeSut() 57 | validatorMock.Output = errors.New("validation error") 58 | 59 | fakeInput := `{ 60 | "userName": "any_username", 61 | "email": "any_email@gmail.com", 62 | "password": "any_password", 63 | "confirmPassword": "any_password" 64 | } 65 | ` 66 | 67 | fakeRequest := protocols.NewHttpRequest([]byte(fakeInput), nil) 68 | 69 | httpResponse := sut.Handle(fakeRequest) 70 | 71 | require.Equal(t, http.StatusBadRequest, httpResponse.StatusCode) 72 | require.Equal(t, validatorMock.Output.Error(), httpResponse.Body) 73 | } 74 | -------------------------------------------------------------------------------- /tests/presentation/controllers/get_user_by_id_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | const FAKE_USER_ID string = "any_user_id" 15 | 16 | type GetUserByIdServiceMock struct { 17 | Output *domaindto.GetUserByIdUseCaseOutputDTO 18 | Error error 19 | } 20 | 21 | func (g *GetUserByIdServiceMock) GetUserById(userId string) (*domaindto.GetUserByIdUseCaseOutputDTO, error) { 22 | return g.Output, g.Error 23 | } 24 | 25 | func MakeGetUserByIdControllerSut() (*controllers.GetUserByIdController, *GetUserByIdServiceMock) { 26 | fakeOutput := domaindto.GetUserByIdUseCaseOutputDTO{ID: "any_id", Name: "any_name", Email: "any_email@gmail.com", UserName: "any_username", Books: nil} 27 | serviceMock := &GetUserByIdServiceMock{Error: nil, Output: &fakeOutput} 28 | sut := controllers.NewGetUserByIdController(serviceMock) 29 | return sut, serviceMock 30 | } 31 | 32 | func TestGetUserByIdController_ShouldReturnRightDataOnSuccess(t *testing.T) { 33 | sut, serviceMock := MakeGetUserByIdControllerSut() 34 | 35 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 36 | 37 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 38 | require.Equal(t, serviceMock.Output, httpResponse.Body) 39 | } 40 | 41 | func TestGetUserByIdController_ShouldReturnNotFoundIfServiceReturnsNull(t *testing.T) { 42 | sut, serviceMock := MakeGetUserByIdControllerSut() 43 | serviceMock.Output = nil 44 | expectedError := errors.New("user not found") 45 | 46 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 47 | 48 | require.Equal(t, http.StatusNotFound, httpResponse.StatusCode) 49 | require.Equal(t, expectedError.Error(), httpResponse.Body) 50 | } 51 | 52 | func TestGetUserByIdController_ShouldReturnServerErrorIfServiceReturnsError(t *testing.T) { 53 | sut, serviceMock := MakeGetUserByIdControllerSut() 54 | serviceMock.Error = errors.New("service error") 55 | 56 | httpResponse := sut.Handle(protocols.NewHttpRequest(nil, []byte("any_id"))) 57 | 58 | require.Equal(t, http.StatusInternalServerError, httpResponse.StatusCode) 59 | require.Equal(t, serviceMock.Error.Error(), httpResponse.Body) 60 | } 61 | -------------------------------------------------------------------------------- /tests/presentation/controllers/add_user_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "testing" 7 | 8 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | controllermocks_test "github.com/KPMGE/go-users-clean-api/tests/presentation/controller-mocks" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | const fakeName string = "any_name" 16 | const fakeUserName string = "any_username" 17 | const fakeEmail string = "any_valid_email@gmail.com" 18 | 19 | type AddUserServiceMock struct { 20 | Output *domaindto.AddUserOutputDTO 21 | Error error 22 | } 23 | 24 | func (s *AddUserServiceMock) Add(input *domaindto.AddUserInputDTO) (*domaindto.AddUserOutputDTO, error) { 25 | return s.Output, s.Error 26 | } 27 | 28 | func MakeFakeAddUserServiceMock() *AddUserServiceMock { 29 | return &AddUserServiceMock{ 30 | Output: domaindto.NewAddUserOutputDTO("any_id", "any_name", "any_username", "any_valid_email@gmail.com"), 31 | Error: nil, 32 | } 33 | } 34 | 35 | func makeAddUserControllerSut() (*controllers.AddUserController, *AddUserServiceMock, *controllermocks_test.ValidatorMock) { 36 | serviceMock := MakeFakeAddUserServiceMock() 37 | validatorMock := controllermocks_test.ValidatorMock{Output: nil} 38 | sut := controllers.NewAddUserController(serviceMock, &validatorMock) 39 | return sut, serviceMock, &validatorMock 40 | } 41 | 42 | func makeFakeAddUserRequest() *protocols.HttpRequest { 43 | fakeBody := `{ 44 | "name": "any_name", 45 | "userName": "any_username", 46 | "email": "any_email@gmail.com" 47 | }` 48 | 49 | fakeRequest := protocols.NewHttpRequest([]byte(fakeBody), nil) 50 | return fakeRequest 51 | } 52 | 53 | func TestAdduserController_ShouldReturnRightDataOnSuccess(t *testing.T) { 54 | sut, serviceMock, _ := makeAddUserControllerSut() 55 | 56 | httpResponse := sut.Handle(makeFakeAddUserRequest()) 57 | 58 | require.Equal(t, http.StatusOK, httpResponse.StatusCode) 59 | require.Equal(t, serviceMock.Output, httpResponse.Body) 60 | } 61 | 62 | func TestAdduserController_ShouldReturnBadRequestIfValidatorReturnsError(t *testing.T) { 63 | sut, _, validatorMock := makeAddUserControllerSut() 64 | validatorMock.Output = errors.New("validation error") 65 | 66 | httpResponse := sut.Handle(makeFakeAddUserRequest()) 67 | 68 | require.Equal(t, http.StatusBadRequest, httpResponse.StatusCode) 69 | require.Equal(t, validatorMock.Output.Error(), httpResponse.Body) 70 | } 71 | -------------------------------------------------------------------------------- /tests/presentation/controllers/add_book_test.go: -------------------------------------------------------------------------------- 1 | package controllers_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 9 | "github.com/KPMGE/go-users-clean-api/src/presentation/controllers" 10 | "github.com/KPMGE/go-users-clean-api/src/presentation/protocols" 11 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 12 | controllermocks_test "github.com/KPMGE/go-users-clean-api/tests/presentation/controller-mocks" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | var FAKE_ADD_BOOK_INPUT = `{ 17 | "title": "any_title", 18 | "author": "any_author", 19 | "price": 123.3, 20 | "description": "any_description", 21 | "userId": "any_user_id" 22 | }` 23 | 24 | var FAKE_REQUEST = protocols.NewHttpRequest([]byte(FAKE_ADD_BOOK_INPUT), nil) 25 | 26 | func MakeAddBookControllerSut() (*controllers.AddBookController, *mocks_test.AddBookRepositorySpy, *controllermocks_test.ValidatorMock) { 27 | bookRepo := mocks_test.NewAddBookRepositorySpy() 28 | userRepo := mocks_test.NewUserRepositorySpy() 29 | fakeUser, _ := entities.NewUser("any_name", "any_username", "any_email@gmail.com") 30 | userRepo.GetByidOutput = fakeUser 31 | service := services.NewAddBookService(bookRepo, userRepo) 32 | validator := &controllermocks_test.ValidatorMock{Output: nil} 33 | sut := controllers.NewAddBookController(service, validator) 34 | return sut, bookRepo, validator 35 | } 36 | 37 | func TestAddBookController_ShouldCallUseCaseWithRightData(t *testing.T) { 38 | sut, bookRepo, _ := MakeAddBookControllerSut() 39 | 40 | sut.Handle(FAKE_REQUEST) 41 | 42 | require.Equal(t, "any_author", bookRepo.Input.Author) 43 | require.Equal(t, "any_title", bookRepo.Input.Title) 44 | require.Equal(t, 123.3, bookRepo.Input.Price) 45 | require.Equal(t, "any_description", bookRepo.Input.Description) 46 | } 47 | 48 | func TestAddBookController_ShouldReturnErrorIfUseCaseReturnsError(t *testing.T) { 49 | sut, bookRepo, _ := MakeAddBookControllerSut() 50 | bookRepo.OutputError = errors.New("book repo error") 51 | 52 | httpResponse := sut.Handle(FAKE_REQUEST) 53 | 54 | require.Equal(t, 400, httpResponse.StatusCode) 55 | require.Equal(t, bookRepo.OutputError.Error(), httpResponse.Body) 56 | } 57 | 58 | func TestAddBookController_ShouldReturnServerErrorIfValidatorReturnsError(t *testing.T) { 59 | sut, _, validatorMock := MakeAddBookControllerSut() 60 | validatorMock.Output = errors.New("validation error") 61 | 62 | httpResponse := sut.Handle(FAKE_REQUEST) 63 | 64 | require.Equal(t, 400, httpResponse.StatusCode) 65 | require.Equal(t, validatorMock.Output.Error(), httpResponse.Body) 66 | } 67 | -------------------------------------------------------------------------------- /src/infrasctructure/repositories/postgres-repository/postgres-users.go: -------------------------------------------------------------------------------- 1 | package postgresrepository 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type PostgresUserRepository struct { 12 | db *gorm.DB 13 | } 14 | 15 | func (repo *PostgresUserRepository) Save(user *entities.User) error { 16 | user.Books = []entities.Book{} 17 | result := repo.db.Create(user) 18 | 19 | if result.Error != nil { 20 | return result.Error 21 | } 22 | 23 | return nil 24 | } 25 | 26 | func (repo *PostgresUserRepository) CheckByEmail(email string) bool { 27 | var user entities.User 28 | 29 | result := repo.db.First(&user, fmt.Sprintf("email='%s'", email)) 30 | 31 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 32 | return false 33 | } 34 | 35 | CheckError(result.Error) 36 | 37 | return true 38 | } 39 | 40 | func (repo *PostgresUserRepository) CheckByUserName(userName string) bool { 41 | var user entities.User 42 | 43 | result := repo.db.First(&user, fmt.Sprintf("user_name = '%s'", userName)) 44 | 45 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 46 | return false 47 | } 48 | 49 | CheckError(result.Error) 50 | 51 | return true 52 | } 53 | 54 | func (repo *PostgresUserRepository) List() []*entities.User { 55 | var users []*entities.User 56 | var books []entities.Book 57 | 58 | resultUsers := repo.db.Find(&users) 59 | CheckError(resultUsers.Error) 60 | 61 | for _, user := range users { 62 | resultBooks := repo.db.Find(&books, fmt.Sprintf("user_id = '%s'", user.ID)) 63 | CheckError(resultBooks.Error) 64 | user.Books = books 65 | } 66 | 67 | return users 68 | } 69 | 70 | func (repo *PostgresUserRepository) Delete(userId string) error { 71 | result := repo.db.Delete(&entities.User{}, fmt.Sprintf("id = '%s'", userId)) 72 | return result.Error 73 | } 74 | 75 | func (repo *PostgresUserRepository) CheckById(userId string) bool { 76 | var user entities.User 77 | 78 | result := repo.db.First(&user, fmt.Sprintf("id = '%s'", userId)) 79 | 80 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 81 | return false 82 | } 83 | 84 | CheckError(result.Error) 85 | 86 | return true 87 | } 88 | 89 | func (repo *PostgresUserRepository) GetById(userId string) (*entities.User, error) { 90 | var user entities.User 91 | 92 | result := repo.db.First(&user, fmt.Sprintf("id = '%s'", userId)) 93 | 94 | if errors.Is(result.Error, gorm.ErrRecordNotFound) { 95 | return nil, errors.New("user not found") 96 | } 97 | CheckError(result.Error) 98 | 99 | return &user, nil 100 | } 101 | 102 | func NewPostgresUserRepository(db *gorm.DB) *PostgresUserRepository { 103 | return &PostgresUserRepository{ 104 | db: db, 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go users clean-api 2 | 3 | ## Goal 4 | This is a really simple project, the aim here is applying the concepts of clean architecture, design patterns, 5 | tdd and solid principles in order to create an api which is not coupled with external agents. 6 | 7 | 8 | ## Api structure 9 | Basically, this api has got 3 entities: accounts, users and books. Every user is gonna get an account 10 | when using the application. Furthermore, each user has its properties, one of them is the books that user 11 | has uploaded to the api. 12 | 13 | 14 | ## How is this project structured? 15 | Basically in this project, we have got 7 principal folders. The __tests__ folder is where i have been doing the 16 | tests. Naturally, as i am using TDD, i have been testing first! 17 | 18 | The __src__ folder is where all the production code is. There we will find all the clean architecture layers plus 19 | a main layer. The main layer is just the place where all the code is coupled. There is the place you are going 20 | to find a connection to a database or an external framework for example. All the remaining layers are decoupled. 21 | 22 | Now i am going to explain briefly what each of the layers do. If you already know about the clean architecture, this is exactly what Robert C. Marting proposes. 23 | 24 | #### domain 25 | The domain layer is where the core of our application is. You shall find the tiniest pieces of the application 26 | there, specifically in our case, the entities of the application: Account, User and Book 27 | 28 | #### application 29 | the application layer is where the implementation of the useCases are gonna be found. More than that, in this 30 | layer, we do not care about how the system is dealing persistence. So we abstract away any type of database connection for example. In order to do that, we just depend on interfaces instead of concrete implementations. So, later we can replace the implementations with the less of effort. 31 | 32 | #### infrastructure 33 | the infrastructure layer is where we do implement the persistence system and connection with external providers. We do that following the 'rules' defined in the domain layer. Please note that by 'rules' i mean that we implement an interface defined in the domain model, so that later we can pass in that concrete implementation to the useCase and it shall know how to deal with the data the way we want. 34 | 35 | #### presentation 36 | the presentation layer is where we deal with how our api is going to serve its data. In our case, we are using http request/response. But it is important to notice that we are not depending on any framework in this layer. Instead, we create a representation of what is a http request and http response. So that, our application does not care how other frameworks deal with http, we just care about how OUR api is gonna represent and deal with it. 37 | 38 | #### main 39 | As i said before, the main layer is where we couple all the components together. The nicest thing in this case is that, as we have decoupled everything else, it's kind of easy to assemble all the components together. More than that, we can use some design patterns to do that, just like the factory design patter for example. 40 | Finally, to serve the that over http, i am using a library of go called [Fiber](https://gofiber.io/) 41 | 42 | 43 | ## How do i use the api routes? 44 | If you wanna know the api routes and stuff, there is a folder called __documentation__ follow the steps there and you are going to 45 | get your docs! 46 | -------------------------------------------------------------------------------- /tests/application/services/add_user_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/KPMGE/go-users-clean-api/src/application/services" 8 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 9 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func makeAddUserSut() (*services.AddUserService, *mocks_test.UserRepositorySpy) { 14 | repo := mocks_test.NewUserRepositorySpy() 15 | repo.AddOutput = nil 16 | sut := services.NewAddUserService(repo) 17 | return sut, repo 18 | } 19 | 20 | func makeFakeValidAddUserInput() *domaindto.AddUserInputDTO { 21 | return domaindto.NewAddUserInputDTO("any_name", "any_username", "any_valid_email@gmail.com") 22 | } 23 | 24 | func TestAddUser_WithRightInput(t *testing.T) { 25 | fakeInput := makeFakeValidAddUserInput() 26 | sut, repo := makeAddUserSut() 27 | 28 | output, err := sut.Add(fakeInput) 29 | 30 | require.Nil(t, err) 31 | require.Equal(t, output.Email, fakeInput.Email) 32 | require.Equal(t, output.UserName, fakeInput.UserName) 33 | require.Equal(t, output.Name, fakeInput.Name) 34 | require.NotNil(t, output.ID) 35 | require.Equal(t, repo.AddInput.Name, fakeInput.Name) 36 | require.Equal(t, repo.AddInput.Email, fakeInput.Email) 37 | require.Equal(t, repo.AddInput.UserName, fakeInput.UserName) 38 | } 39 | 40 | func TestAddUser_WithInvalidEmail(t *testing.T) { 41 | sut, _ := makeAddUserSut() 42 | fakeInput := makeFakeValidAddUserInput() 43 | fakeInput.Email = "invalid_email" 44 | 45 | output, err := sut.Add(fakeInput) 46 | 47 | require.Error(t, err) 48 | require.Equal(t, "Invalid email!", err.Error()) 49 | require.Nil(t, output) 50 | } 51 | 52 | func TestAddUser_WithBlankFields(t *testing.T) { 53 | sut, _ := makeAddUserSut() 54 | 55 | fakeInput := makeFakeValidAddUserInput() 56 | fakeInput.Name = "" 57 | output, err := sut.Add(fakeInput) 58 | require.Error(t, err) 59 | require.Nil(t, output) 60 | 61 | fakeInput = makeFakeValidAddUserInput() 62 | fakeInput.UserName = "" 63 | output, err = sut.Add(fakeInput) 64 | require.Error(t, err) 65 | require.Nil(t, output) 66 | 67 | fakeInput = makeFakeValidAddUserInput() 68 | fakeInput.Email = "" 69 | output, err = sut.Add(fakeInput) 70 | require.Error(t, err) 71 | require.Nil(t, output) 72 | } 73 | 74 | func TestAddUser_WhenRepositoryReturnsError(t *testing.T) { 75 | sut, repo := makeAddUserSut() 76 | repo.AddOutput = errors.New("some error") 77 | fakeInput := makeFakeValidAddUserInput() 78 | 79 | output, err := sut.Add(fakeInput) 80 | 81 | require.Nil(t, output) 82 | require.Error(t, err) 83 | require.Equal(t, err.Error(), "some error") 84 | } 85 | 86 | func TestAddUser_WithSameEmail(t *testing.T) { 87 | fakeInput := makeFakeValidAddUserInput() 88 | sut, repo := makeAddUserSut() 89 | repo.CheckByEmailOutput = true 90 | 91 | output, err := sut.Add(fakeInput) 92 | 93 | require.Error(t, err) 94 | require.Equal(t, err.Error(), "email already taken!") 95 | require.Equal(t, fakeInput.Email, repo.CheckByEmailInput) 96 | require.Nil(t, output) 97 | } 98 | 99 | func TestAddUser_WithSameUserName(t *testing.T) { 100 | fakeInput := makeFakeValidAddUserInput() 101 | sut, repo := makeAddUserSut() 102 | repo.CheckByUserNameOutput = true 103 | 104 | output, err := sut.Add(fakeInput) 105 | 106 | require.Error(t, err) 107 | require.Equal(t, err.Error(), "UserName already taken!") 108 | require.Nil(t, output) 109 | } 110 | -------------------------------------------------------------------------------- /tests/application/services/add_account_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "github.com/KPMGE/go-users-clean-api/src/application/services" 5 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 6 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | ) 10 | 11 | const fakeUserName string = "any_user_name" 12 | const fakeEmail string = "any_valid_email@gmail.com" 13 | const fakePassword string = "any_password" 14 | 15 | func makeFakeInput() *domaindto.AddAccountInputDTO { 16 | return domaindto.NewAddAccountInputDTO(fakeUserName, fakeEmail, fakePassword, fakePassword) 17 | } 18 | 19 | func makeSut() (*services.AddAccountService, *mocks_test.HasherSpy, *mocks_test.FakeAccountRepository) { 20 | repo := mocks_test.NewFakeAccountRepository() 21 | hasher := mocks_test.NewHasherSpy() 22 | sut := services.NewAddAccountService(repo, hasher) 23 | return sut, hasher, repo 24 | } 25 | 26 | func TestAddAccountService_WithRightData(t *testing.T) { 27 | sut, hasher, repo := makeSut() 28 | fakeInput := makeFakeInput() 29 | createdAccount, err := sut.AddAccount(fakeInput) 30 | 31 | require.Nil(t, err) 32 | require.Equal(t, hasher.Input, fakePassword) 33 | require.Equal(t, createdAccount.Email, fakeEmail) 34 | require.Equal(t, createdAccount.UserName, fakeUserName) 35 | require.Equal(t, repo.Input.Password, "hashed_text") 36 | } 37 | 38 | func TestAddAccountService_WithDifferentPasswordAndConfirmPassword(t *testing.T) { 39 | sut, _, _ := makeSut() 40 | fakeInput := makeFakeInput() 41 | fakeInput.ConfirmPassword = "any_different_password" 42 | 43 | createdAccount, err := sut.AddAccount(fakeInput) 44 | 45 | require.Error(t, err) 46 | require.Nil(t, createdAccount) 47 | require.Equal(t, err.Error(), "password and confirmPassword must match") 48 | } 49 | 50 | func TestAddAccountService_WithEmailAlreadyTaken(t *testing.T) { 51 | sut, _, repo := makeSut() 52 | repo.CheckAccountOutput = true 53 | fakeInput := makeFakeInput() 54 | 55 | createdAccount, err := sut.AddAccount(fakeInput) 56 | 57 | require.Error(t, err) 58 | require.Nil(t, createdAccount) 59 | require.Equal(t, err.Error(), "email already taken") 60 | } 61 | 62 | func TestAddAccountService_WithUsernameAlreadyTaken(t *testing.T) { 63 | sut, _, repo := makeSut() 64 | fakeInput := makeFakeInput() 65 | repo.CheckUserNameOutput = true 66 | 67 | createdAccount, err := sut.AddAccount(fakeInput) 68 | 69 | require.Error(t, err) 70 | require.Nil(t, createdAccount) 71 | require.Equal(t, err.Error(), "username already taken") 72 | } 73 | 74 | func TestAddAccountService_WithBlankFields(t *testing.T) { 75 | sut, _, repo := makeSut() 76 | repo.CheckUserNameOutput = false 77 | repo.CheckUserNameOutput = false 78 | 79 | fakeInput := makeFakeInput() 80 | fakeInput.UserName = "" 81 | createdAccount, err := sut.AddAccount(fakeInput) 82 | require.Error(t, err) 83 | require.Nil(t, createdAccount) 84 | 85 | fakeInput = makeFakeInput() 86 | fakeInput.Password = "" 87 | createdAccount, err = sut.AddAccount(fakeInput) 88 | require.Error(t, err) 89 | require.Nil(t, createdAccount) 90 | 91 | fakeInput = makeFakeInput() 92 | fakeInput.ConfirmPassword = "" 93 | createdAccount, err = sut.AddAccount(fakeInput) 94 | require.Error(t, err) 95 | require.Nil(t, createdAccount) 96 | 97 | fakeInput = makeFakeInput() 98 | fakeInput.Email = "" 99 | createdAccount, err = sut.AddAccount(fakeInput) 100 | require.Error(t, err) 101 | require.Nil(t, createdAccount) 102 | } 103 | -------------------------------------------------------------------------------- /tests/application/services/add_book_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/application/services" 9 | domaindto "github.com/KPMGE/go-users-clean-api/src/domain/domain-dto" 10 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 11 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 12 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | var FAKE_ADD_BOOK_INPUT_DTO = &domaindto.AddBookUseCaseInputDTO{ 17 | Title: "any_title", 18 | Author: "any_author", 19 | Price: 342.2, 20 | Description: "any_description", 21 | UserId: "any_valid_user_id", 22 | } 23 | 24 | func MakeAddBookSut() (usecases.AddBookUseCase, *mocks_test.AddBookRepositorySpy, *mocks_test.UserRepositorySpy) { 25 | fakeUser, err := entities.NewUser("any_name", "any_username", "any_email@gmail.com") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | userRepo := mocks_test.NewUserRepositorySpy() 31 | userRepo.GetByidOutput = fakeUser 32 | userRepo.GetByidError = nil 33 | 34 | bookRepo := mocks_test.NewAddBookRepositorySpy() 35 | bookRepo.OutputError = nil 36 | sut := services.NewAddBookService(bookRepo, userRepo) 37 | 38 | return sut, bookRepo, userRepo 39 | } 40 | 41 | func TestAddBookUseCase_ShouldCallRepositoryWithRightData(t *testing.T) { 42 | sut, bookRepo, _ := MakeAddBookSut() 43 | 44 | sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 45 | require.Equal(t, FAKE_ADD_BOOK_INPUT_DTO.Description, bookRepo.Input.Description) 46 | require.Equal(t, FAKE_ADD_BOOK_INPUT_DTO.Price, bookRepo.Input.Price) 47 | require.Equal(t, FAKE_ADD_BOOK_INPUT_DTO.Title, bookRepo.Input.Title) 48 | require.NotNil(t, bookRepo.Input.ID) 49 | require.NotNil(t, bookRepo.Input.UserID) 50 | } 51 | 52 | func TestAddBookUseCase_ShouldCallUserRepositoryWithRightUserId(t *testing.T) { 53 | sut, _, userRepo := MakeAddBookSut() 54 | 55 | sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 56 | 57 | require.Equal(t, FAKE_ADD_BOOK_INPUT_DTO.UserId, userRepo.GetByidInput) 58 | } 59 | 60 | func TestAddBookUseCase_ShouldReturnErrorIfWrongUserIdIsGiven(t *testing.T) { 61 | sut, _, userRepo := MakeAddBookSut() 62 | userRepo.GetByidOutput = nil 63 | 64 | output, err := sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 65 | 66 | require.Nil(t, output) 67 | require.Error(t, err) 68 | require.Equal(t, "User not found!", err.Error()) 69 | } 70 | 71 | func TestAddBookUseCase_ShouldReturnErrorUserRepositoryReturnsError(t *testing.T) { 72 | sut, _, userRepo := MakeAddBookSut() 73 | userRepo.GetByidError = errors.New("repo error") 74 | 75 | output, err := sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 76 | 77 | require.Nil(t, output) 78 | require.Error(t, err) 79 | require.Equal(t, "repo error", err.Error()) 80 | } 81 | 82 | func TestAddBookUseCase_ShouldReturnOuputDTO(t *testing.T) { 83 | sut, bookRepo, _ := MakeAddBookSut() 84 | 85 | output, err := sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 86 | 87 | require.Nil(t, err) 88 | require.Equal(t, output.Title, bookRepo.Output.Title) 89 | require.Equal(t, output.Author, bookRepo.Output.Author) 90 | require.Equal(t, output.Price, bookRepo.Output.Price) 91 | require.Equal(t, output.Description, bookRepo.Output.Description) 92 | require.NotNil(t, output.ID) 93 | } 94 | 95 | func TestAddBookUseCase_ShouldReturnErrorIfAddBookReturnsError(t *testing.T) { 96 | sut, bookRepo, _ := MakeAddBookSut() 97 | bookRepo.OutputError = errors.New("add book error") 98 | 99 | output, err := sut.AddBook(FAKE_ADD_BOOK_INPUT_DTO) 100 | 101 | require.Error(t, err) 102 | require.Nil(t, output) 103 | require.Equal(t, "add book error", err.Error()) 104 | } 105 | -------------------------------------------------------------------------------- /tests/application/services/remove_book_test.go: -------------------------------------------------------------------------------- 1 | package usecases_test 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "testing" 7 | 8 | "github.com/KPMGE/go-users-clean-api/src/application/services" 9 | "github.com/KPMGE/go-users-clean-api/src/domain/entities" 10 | usecases "github.com/KPMGE/go-users-clean-api/src/domain/useCases" 11 | mocks_test "github.com/KPMGE/go-users-clean-api/tests/application/mocks" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func MakeRemoveBookSut() ( 16 | usecases.RemoveBookUseCase, 17 | *mocks_test.RemoveBookRepositorySpy, 18 | *mocks_test.FindBookRepositorySpy, 19 | *mocks_test.UserRepositorySpy, 20 | ) { 21 | removeBookRepo := mocks_test.NewRemoveBookRepositorySpy() 22 | removeBookRepo.RemoveError = nil 23 | 24 | findBookRepo := mocks_test.NewFindBookRepositorySpy() 25 | findBookRepo.FindError = nil 26 | fakeBook, err := entities.NewBook("any_title", "any_author", "any_description", 100.2, "any_user_id") 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | findBookRepo.FindOutput = fakeBook 31 | 32 | userRepo := mocks_test.NewUserRepositorySpy() 33 | fakeUser, err := entities.NewUser("any_name", "any_username", "any_valid_email@gmail.com") 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | fakeUser.ID = "any_user_id" 38 | fakeBook.ID = "any_valid_book_id" 39 | fakeUser.Books = append(fakeUser.Books, *fakeBook) 40 | userRepo.GetByidOutput = fakeUser 41 | 42 | sut := services.NewRemoveBookService(removeBookRepo, findBookRepo, userRepo) 43 | return sut, removeBookRepo, findBookRepo, userRepo 44 | } 45 | 46 | func TestRemoveBookUseCase_ShouldCallRepositoryWithRightBookId(t *testing.T) { 47 | sut, removeBookRepo, _, _ := MakeRemoveBookSut() 48 | 49 | sut.RemoveBook("any_valid_book_id") 50 | 51 | require.Equal(t, "any_valid_book_id", removeBookRepo.RemoveInput) 52 | } 53 | 54 | func TestRemoveBookUseCase_ShouldReturnErrorIfRepositoryReturnsError(t *testing.T) { 55 | sut, removeBookRepo, _, _ := MakeRemoveBookSut() 56 | removeBookRepo.RemoveError = errors.New("repo error") 57 | 58 | deletedBook, err := sut.RemoveBook("any_invalid_id") 59 | 60 | require.Nil(t, deletedBook) 61 | require.Error(t, err) 62 | require.Equal(t, "repo error", err.Error()) 63 | } 64 | 65 | func TestRemoveBookUseCase_ShouldCallFindBookRepositoryWithRightBookId(t *testing.T) { 66 | sut, _, findBookRepo, _ := MakeRemoveBookSut() 67 | 68 | sut.RemoveBook("any_book_id") 69 | 70 | require.Equal(t, "any_book_id", findBookRepo.FindInput) 71 | } 72 | 73 | func TestRemoveBookUseCase_ShouldReturnErrorIfFindBookReturnsNil(t *testing.T) { 74 | sut, _, findBookRepo, _ := MakeRemoveBookSut() 75 | findBookRepo.FindOutput = nil 76 | 77 | deletedBook, err := sut.RemoveBook("any_book_id") 78 | 79 | require.Nil(t, deletedBook) 80 | require.Error(t, err) 81 | require.Equal(t, "book not found!", err.Error()) 82 | } 83 | 84 | func TestRemoveBookUseCase_ShouldReturnErrorIfFindBookReturnsError(t *testing.T) { 85 | sut, _, findBookRepo, _ := MakeRemoveBookSut() 86 | findBookRepo.FindError = errors.New("repo error") 87 | 88 | deletedBook, err := sut.RemoveBook("any_book_id") 89 | 90 | require.Nil(t, deletedBook) 91 | require.Error(t, err) 92 | require.Equal(t, "repo error", err.Error()) 93 | } 94 | 95 | func TestRemoveBookUseCase_ShouldReturnRightDataOnSuccess(t *testing.T) { 96 | sut, _, findBookRepo, _ := MakeRemoveBookSut() 97 | 98 | deletedBook, err := sut.RemoveBook("any_valid_book_id") 99 | 100 | require.Nil(t, err) 101 | require.Equal(t, findBookRepo.FindOutput.Author, deletedBook.Author) 102 | require.Equal(t, findBookRepo.FindOutput.Price, deletedBook.Price) 103 | require.Equal(t, findBookRepo.FindOutput.Description, deletedBook.Description) 104 | require.Equal(t, findBookRepo.FindOutput.Title, deletedBook.Title) 105 | require.Equal(t, findBookRepo.FindOutput.UserID, deletedBook.UserId) 106 | } 107 | -------------------------------------------------------------------------------- /documentation/golang-api.json: -------------------------------------------------------------------------------- 1 | {"_type":"export","__export_format":4,"__export_date":"2022-08-21T23:00:08.163Z","__export_source":"insomnia.desktop.app:v2022.3.0","resources":[{"_id":"req_d3c5c5b4ab294062973ecf3c63affc2f","parentId":"fld_7bbe782fd13748549c13841603da50a8","modified":1661122566153,"created":1650860455660,"url":"http://127.0.0.1:3333/api/accounts","name":"Create an account","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"email\": \"testmail@gmail.com\",\n\t\"userName\": \"some username\",\n\t\"password\": \"some password\",\n\t\"confirmPassword\": \"some password\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_31b27d0c788e4423b8fc2e569b4a04b3"}],"authentication":{},"metaSortKey":-1651235886593.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_7bbe782fd13748549c13841603da50a8","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1651235888057,"created":1651235888057,"name":"account","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1651235888057,"_type":"request_group"},{"_id":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","parentId":null,"modified":1650234973491,"created":1650234973491,"name":"New Document","description":"","scope":"design","_type":"workspace"},{"_id":"req_176386eeb0f04b70893f9737a467c308","parentId":"fld_7bbe782fd13748549c13841603da50a8","modified":1661122578648,"created":1650860614603,"url":"http://127.0.0.1:3333/api/accounts/","name":"Delete account","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1651235886543.75,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b82a62276e98438b894887818fee2b36","parentId":"fld_857d6ebcba394f26ae3aa0fb05a8a979","modified":1661122661291,"created":1650235030492,"url":"http://127.0.0.1:3333/api/users","name":"Create user","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"some name\",\n\t\"email\": \"testmail@gmail.com\", \n\t\"userName\": \"some username\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_200870918df74d1fa6e93300881d6ea2"}],"authentication":{},"metaSortKey":-1659348663995,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_857d6ebcba394f26ae3aa0fb05a8a979","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1651235875551,"created":1651235875551,"name":"users","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1651235875551,"_type":"request_group"},{"_id":"req_c683f0c3f4c84b638f9e5a5835e7cb83","parentId":"fld_857d6ebcba394f26ae3aa0fb05a8a979","modified":1661122612481,"created":1651238390772,"url":" http://localhost:3333/api/users","name":"List users","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1659348663970,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ef9c41645c354decb87567acd96323b7","parentId":"fld_857d6ebcba394f26ae3aa0fb05a8a979","modified":1661122675630,"created":1659348663945,"url":"http://localhost:3333/api/users/","name":"Delete user","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1659348663945,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":true,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_98a58206a3f740799944f9649acdb216","parentId":"fld_857d6ebcba394f26ae3aa0fb05a8a979","modified":1661122683558,"created":1650336377607,"url":"http://127.0.0.1:3333/api/users/","name":"Get user by id","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1650235030542,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_bcd5da49411b4645ad3839d970ecec18","parentId":"fld_a751f99387954d86bfa00ff62015bfd7","modified":1661122776494,"created":1650675199537,"url":"http://127.0.0.1:3333/api/books","name":"Create book","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"author\": \"some author\",\n\t\"title\": \"some title\", \n\t\"price\": 10000,\n\t\"description\": \"some description\",\n\t\"userId\": \"user id\"\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json","id":"pair_93dc44702ed843eeb528a1727d4fec44"}],"authentication":{},"metaSortKey":-1651235843867,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_a751f99387954d86bfa00ff62015bfd7","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1651235847720,"created":1651235847720,"name":"books","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1651235847720,"_type":"request_group"},{"_id":"req_da2030c135334eb4a8e7321506fd466c","parentId":"fld_a751f99387954d86bfa00ff62015bfd7","modified":1661122744605,"created":1651235843767,"url":"http://127.0.0.1:3333/api/books","name":"List books","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1651235843842,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_4083abc2afc84fc8ade9113046589eb4","parentId":"fld_a751f99387954d86bfa00ff62015bfd7","modified":1661122759984,"created":1651078622200,"url":"http://127.0.0.1:3333/api/books/","name":"Get book by id","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1651235843817,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_50df9c55db0e4d9ab2991edb080c0d04","parentId":"fld_a751f99387954d86bfa00ff62015bfd7","modified":1661122768771,"created":1650814429012,"url":"http://127.0.0.1:3333/api/books/","name":"Remove book","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1651235843779.5,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_7d8b95cfc41bda56eee803803a16845608de09e0","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1650234973509,"created":1650234973509,"name":"Base Environment","data":{},"dataPropertyOrder":null,"color":null,"isPrivate":false,"metaSortKey":1650234973509,"_type":"environment"},{"_id":"jar_7d8b95cfc41bda56eee803803a16845608de09e0","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1660881676286,"created":1650234973515,"name":"Default Jar","cookies":[{"key":"1P_JAR","value":"2022-08-19-04","expires":"2022-09-18T04:01:48.000Z","domain":"google.com","path":"/","secure":true,"hostOnly":false,"creation":"2022-08-19T04:01:16.090Z","lastAccessed":"2022-08-19T04:01:16.090Z","id":"6060618018338171"},{"key":"AEC","value":"AakniGNcrsS8KZUu_rElSjRA5m1kOPxsYM17Ufy7blUZ3gn86A7TyXIJ6Q","expires":"2023-02-15T04:01:48.000Z","domain":"google.com","path":"/","secure":true,"httpOnly":true,"extensions":["SameSite=lax"],"hostOnly":false,"creation":"2022-08-19T04:01:16.106Z","lastAccessed":"2022-08-19T04:01:16.106Z","id":"13268434797242312"},{"key":"NID","value":"511=TbbSWC4g_QECuiHYObB0UJBak_wTe__5ohRkUl8Tv8JRIVXjR2BBiYmVDSmcbtkxumXRefP6CoAPZ3xYlkwwsZyEJV6PbUklMFQjIef9_KTOhOf_5JBYXWhvSGXI_qghh5yenKWi7s5E8d163rUW8XFK4CeYBnOZmxSp5lDLBrk","expires":"2023-02-18T04:01:48.000Z","domain":"google.com","path":"/","httpOnly":true,"hostOnly":false,"creation":"2022-08-19T04:01:16.107Z","lastAccessed":"2022-08-19T04:01:16.107Z","id":"32688020612012636"}],"_type":"cookie_jar"},{"_id":"spc_8346aa1fa4e54082a98cb67bb0b15bc8","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1659091123305,"created":1650234973502,"fileName":"go users clean api","contents":"","contentType":"yaml","_type":"api_spec"},{"_id":"uts_f3d6d88da5de4b2ba8ec2a0ece53f5aa","parentId":"wrk_338842f91b5e40bdbe49cae7ba3e0ca3","modified":1650234973522,"created":1650234973522,"name":"Example Test Suite","_type":"unit_test_suite"}]} -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 3 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 4 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 5 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 7 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 8 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 9 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 10 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 15 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 16 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 17 | github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow= 18 | github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= 19 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 20 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 21 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 22 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 23 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 24 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 25 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 26 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 27 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 28 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 29 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 30 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 31 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 32 | github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= 33 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= 34 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 35 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 36 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 37 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 38 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 39 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 40 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 41 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 42 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 43 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 44 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 45 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 46 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 47 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 48 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 49 | github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= 50 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 51 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 52 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 53 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 54 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 55 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 56 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 57 | github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= 58 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 59 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 60 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 61 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 62 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 63 | github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= 64 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= 65 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 66 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 67 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 68 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 69 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 70 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 71 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 72 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 73 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 74 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 75 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 76 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 77 | github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= 78 | github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 79 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 80 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 81 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 82 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 83 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 84 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 85 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 86 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 87 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 88 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 89 | github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= 90 | github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 91 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 92 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 93 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 94 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 95 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 96 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 97 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 98 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 99 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 100 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 101 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 102 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 103 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 104 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 105 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 106 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 107 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 108 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 109 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 110 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 111 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 112 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 113 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 114 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 115 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 116 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= 118 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 119 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 120 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 121 | github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= 122 | github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= 123 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 124 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 125 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 126 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 127 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 128 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 129 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 130 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 131 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 132 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 133 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 134 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 135 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 136 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 137 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 138 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 139 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 140 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 142 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 143 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 144 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 145 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 146 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 147 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 148 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= 149 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 150 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= 151 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 152 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 153 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 154 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 155 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 156 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 157 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 158 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 159 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 160 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 161 | golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 162 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 163 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 165 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 166 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= 178 | golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 179 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 180 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 181 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 182 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 183 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 184 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 185 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 186 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 187 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 188 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 189 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 190 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 191 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 192 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 193 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 194 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 195 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 196 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 197 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 198 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 200 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 202 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 203 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 204 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 205 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 206 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 207 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 208 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 209 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 210 | gorm.io/driver/postgres v1.3.8 h1:8bEphSAB69t3odsCR4NDzt581iZEWQuRM27Cg6KgfPY= 211 | gorm.io/driver/postgres v1.3.8/go.mod h1:qB98Aj6AhRO/oyu/jmZsi/YM9g6UzVCjMxO/6frFvcA= 212 | gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 213 | gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= 214 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 215 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 216 | --------------------------------------------------------------------------------