diff --git a/internal/adapters/http/handlers/cliente.go b/internal/adapters/http/handlers/cliente.go index 1a1e87f..6f90d10 100644 --- a/internal/adapters/http/handlers/cliente.go +++ b/internal/adapters/http/handlers/cliente.go @@ -32,7 +32,7 @@ func NewCliente(cadastraClienteUC usecase.CadastrarClienteUseCase, pegaClientePo func (h *Cliente) RegistraRotasCliente(server *echo.Echo) { server.POST("/cliente", h.cadastra) - server.GET("/clientes/:cpf", h.pegaPorCpf, h.tokenJwt.VerifyToken) + server.GET("/clientes/:cpf", h.pegaPorCpf) server.GET("/internal/clientes/:id", h.pegaPorID) server.DELETE("/lgpd/clientes/delete", h.anonimizarClientLGPD) } @@ -43,11 +43,11 @@ func (h *Cliente) RegistraRotasCliente(server *echo.Echo) { // @Accept json // @Produce json // @Param pedido body domain.ClienteRequest true "cria novo cliente" -// @Success 200 {object} domain.Cliente +// @Success 200 {object} domain.ClienteResponse // @Router /cliente [post] func (h *Cliente) cadastra(ctx echo.Context) error { var ( - cliente domain.Cliente + cliente domain.ClienteRequest err error ) @@ -74,13 +74,13 @@ func (h *Cliente) cadastra(ctx echo.Context) error { // @Produce json // @Param Authorization header string true "Insert your access token" default(Bearer ) // @Param cpf path string true "cpf do cliente" -// @Success 200 {object} domain.Cliente +// @Success 200 {object} domain.ClienteResponse // @Router /clientes/{cpf} [get] func (h *Cliente) pegaPorCpf(ctx echo.Context) error { cpf := ctx.Param("cpf") - - c := &domain.Cliente{ - Cpf: &cpf, + + c := &domain.ClienteRequest{ + Cpf: cpf, } if err := c.ValidateCPF(); err != nil { @@ -99,7 +99,7 @@ func (h *Cliente) pegaPorCpf(ctx echo.Context) error { ctx.Response().Header().Set("Authorization", token) - return ctx.JSON(http.StatusOK, cliente) + return ctx.JSON(http.StatusOK, domain.NewClienteResponse(cliente)) } // pegaPorID godoc @@ -109,7 +109,7 @@ func (h *Cliente) pegaPorCpf(ctx echo.Context) error { // @Produce json // @Param Authorization header string true "Insert your access token" default(Bearer ) // @Param id path string true "id do cliente" -// @Success 200 {object} domain.Cliente +// @Success 200 {object} domain.ClienteResponse // @Router /clientes/{id} [get] func (h *Cliente) pegaPorID(ctx echo.Context) error { id := ctx.Param("id") @@ -126,7 +126,7 @@ func (h *Cliente) pegaPorID(ctx echo.Context) error { return ctx.JSON(http.StatusOK, cliente) } -func (h *Cliente) validateClienteBody(c *domain.Cliente) error { +func (h *Cliente) validateClienteBody(c *domain.ClienteRequest) error { if err := h.validator.ValidateStruct(c); err != nil { return err } @@ -144,14 +144,14 @@ func (h *Cliente) validateClienteBody(c *domain.Cliente) error { // @Accept json // @Produce json // @Param pedido body domain.LGPDClienteRequest true "anonimiza os dados do cliente" -// @Success 200 {object} domain.Cliente +// @Success 200 {object} domain.ClienteResponse // @Router /lgpd/clientes/delete [delete] func (h *Cliente) anonimizarClientLGPD(ctx echo.Context) error { var ( - cliente domain.Cliente + cliente domain.ClienteRequest err error ) - + if err = ctx.Bind(&cliente); err != nil { return serverErr.HandleError(ctx, serverErr.BadRequest.New(err.Error())) } @@ -164,7 +164,7 @@ func (h *Cliente) anonimizarClientLGPD(ctx echo.Context) error { if err != nil { return serverErr.HandleError(ctx, errorx.Cast(err)) } - + return ctx.NoContent(http.StatusOK) } diff --git a/internal/adapters/http/handlers/login.go b/internal/adapters/http/handlers/login.go index 9922d0c..760ed70 100644 --- a/internal/adapters/http/handlers/login.go +++ b/internal/adapters/http/handlers/login.go @@ -35,8 +35,8 @@ func (h *Login) RegistraRotasLogin(server *echo.Echo) { // @Router /login/{cpf} [get] func (h *Login) login(ctx echo.Context) error { cpf := ctx.Param("cpf") - c := &domain.Cliente{ - Cpf: &cpf, + c := &domain.ClienteRequest{ + Cpf: cpf, } if err := c.ValidateCPF(); err != nil { diff --git a/internal/adapters/repository/cliente.go b/internal/adapters/repository/cliente.go index 8b6c3e4..b7b7764 100644 --- a/internal/adapters/repository/cliente.go +++ b/internal/adapters/repository/cliente.go @@ -3,9 +3,9 @@ package repository import ( "context" "fiap-tech-challenge-api/internal/core/domain" + "github.com/joomcode/errorx" db "github.com/rhuandantas/fiap-tech-challenge-commons/pkg/db/mysql" _error "github.com/rhuandantas/fiap-tech-challenge-commons/pkg/errors" - "github.com/joomcode/errorx" "xorm.io/xorm" ) @@ -20,7 +20,6 @@ type ClienteRepo interface { PesquisaPorCPF(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) PesquisaPorId(ctx context.Context, id int64) (*domain.Cliente, error) Anonimizar(ctx context.Context, cliente *domain.Cliente) error - } func NewClienteRepo(connector db.DBConnector) ClienteRepo { @@ -79,28 +78,10 @@ func (r *cliente) PesquisaPorId(ctx context.Context, id int64) (*domain.Cliente, return &c, nil } -func (r *cliente) Anonimizar(ctx context.Context, cliente *domain.Cliente) error { - - newCliente := domain.Cliente{ - Cpf: nil, - Nome: nil, - Email: nil, - Telefone: nil, - } - - found, err := r.session.Context(ctx).Get(&cliente) - if err != nil { - return err - } - - if !found { - return _error.NotFound.New("cliente não encontrado") - } - - affected, err := r.session.Context(ctx).ID(&cliente.Id).Update(newCliente) +func (r *cliente) Anonimizar(ctx context.Context, cli *domain.Cliente) error { + _, err := r.session.Context(ctx).ID(cli.Id).Update(cli) if err != nil { return errorx.InternalError.New(err.Error()) } - _ = affected return nil -} \ No newline at end of file +} diff --git a/internal/core/domain/cliente.go b/internal/core/domain/cliente.go index 2b729fb..0eb8030 100644 --- a/internal/core/domain/cliente.go +++ b/internal/core/domain/cliente.go @@ -2,6 +2,7 @@ package domain import ( "bytes" + "database/sql" "errors" "fiap-tech-challenge-api/internal/core/commons" "time" @@ -16,23 +17,59 @@ type ClienteRequest struct { Email string `json:"email" validate:"email"` Telefone string `json:"telefone"` } +type ClienteResponse struct { + Cpf string `json:"cpf" validate:"required" xorm:"unique"` + Nome string `json:"nome" validate:"required"` + Email string `json:"email" validate:"email"` + Telefone string `json:"telefone"` + CreatedAt time.Time `json:"created_at" xorm:"created"` + UpdatedAt time.Time `json:"updated_at" xorm:"updated"` +} type LGPDClienteRequest struct { - Cpf string `json:"cpf" validate:"required" xorm:"unique"` + Cpf string `json:"cpf" validate:"required" xorm:"unique"` } type Cliente struct { - Id int64 `json:"id" xorm:"pk autoincr 'cliente_id'"` - Cpf *string `json:"cpf" xorm:"null unique"` - Nome *string `json:"nome" xorm:"null"` - Email *string `json:"email" xorm:"null"` - Telefone *string `json:"telefone" xorm:"null"` - CreatedAt time.Time `json:"created_at" xorm:"created"` - UpdatedAt time.Time `json:"updated_at" xorm:"updated"` + Id int64 `json:"id" xorm:"pk autoincr 'cliente_id'"` + Cpf sql.NullString `json:"cpf" xorm:"null"` + Nome sql.NullString `json:"nome" xorm:"null"` + Email sql.NullString `json:"email" xorm:"null"` + Telefone sql.NullString `json:"telefone" xorm:"null"` + CreatedAt time.Time `json:"created_at" xorm:"created"` + UpdatedAt time.Time `json:"updated_at" xorm:"updated"` +} + +func NewClient(cli *ClienteRequest) *Cliente { + return &Cliente{ + Cpf: setNullString(cli.Cpf), + Nome: setNullString(cli.Nome), + Email: setNullString(cli.Email), + Telefone: setNullString(cli.Telefone), + } } -func (c *Cliente) ValidateCPF() error { - if !brdoc.IsCPF(DerefString(c.Cpf)) { +func NewClienteResponse(cli *Cliente) *ClienteResponse { + return &ClienteResponse{ + Cpf: cli.Cpf.String, + Nome: cli.Nome.String, + Email: cli.Email.String, + Telefone: cli.Telefone.String, + CreatedAt: cli.CreatedAt, + UpdatedAt: cli.UpdatedAt, + } +} + +func setNullString(str string) sql.NullString { + if str == "" { + return sql.NullString{} + } + + return sql.NullString{String: str, Valid: true} +} + +func (c *ClienteRequest) ValidateCPF() error { + if !brdoc.IsCPF(c.Cpf) { return errors.New(commons.CpfInvalido) } @@ -42,22 +79,20 @@ func (c *Cliente) ValidateCPF() error { } func DerefString(s *string) string { - if s != nil { - return *s - } + if s != nil { + return *s + } - return "" + return "" } - -func (c *Cliente) limpaCaracteresEspeciais() { +func (c *ClienteRequest) limpaCaracteresEspeciais() { buf := bytes.NewBufferString("") - for _, r := range DerefString(c.Cpf) { + for _, r := range c.Cpf { if unicode.IsDigit(r) { buf.WriteRune(r) } } - varAux := string(buf.String()) - c.Cpf = &varAux + c.Cpf = buf.String() } diff --git a/internal/core/usecase/cadastra_cliente.go b/internal/core/usecase/cadastra_cliente.go index d66d957..4419c00 100644 --- a/internal/core/usecase/cadastra_cliente.go +++ b/internal/core/usecase/cadastra_cliente.go @@ -2,12 +2,14 @@ package usecase import ( "context" + "errors" "fiap-tech-challenge-api/internal/adapters/repository" "fiap-tech-challenge-api/internal/core/domain" + _error "github.com/rhuandantas/fiap-tech-challenge-commons/pkg/errors" ) type CadastrarClienteUseCase interface { - Cadastra(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) + Cadastra(ctx context.Context, cliente *domain.ClienteRequest) (*domain.Cliente, error) } type cadastraClienteUC struct { @@ -20,6 +22,17 @@ func NewCadastraCliente(clienteRepo repository.ClienteRepo) CadastrarClienteUseC } } -func (uc *cadastraClienteUC) Cadastra(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) { - return uc.clienteRepo.Insere(ctx, cliente) +func (uc *cadastraClienteUC) Cadastra(ctx context.Context, cliente *domain.ClienteRequest) (*domain.Cliente, error) { + found, err := uc.clienteRepo.PesquisaPorCPF(ctx, domain.NewClient(cliente)) + if err != nil { + if errors.Is(err, _error.BadRequest.New("cliente não encontrado")) { + return nil, err + } + } + + if found != nil { + return found, _error.BadRequest.New("cliente já existe") + } + + return uc.clienteRepo.Insere(ctx, domain.NewClient(cliente)) } diff --git a/internal/core/usecase/cadastra_cliente_test.go b/internal/core/usecase/cadastra_cliente_test.go index 6c07bee..c155019 100644 --- a/internal/core/usecase/cadastra_cliente_test.go +++ b/internal/core/usecase/cadastra_cliente_test.go @@ -2,6 +2,7 @@ package usecase import ( "context" + "database/sql" "errors" "fiap-tech-challenge-api/internal/core/domain" mock_repo "fiap-tech-challenge-api/test/mock/repository" @@ -25,21 +26,27 @@ var _ = Describe("cadastra cliente use case testes", func() { }) Context("cadastra cliente", func() { - clienteDTO := &domain.Cliente{ - Id: 1, + clienteDTO := &domain.ClienteRequest{ Nome: "Mock", Cpf: "20815919018", Email: "mock@gmail.com", } + dto := &domain.Cliente{ + Nome: sql.NullString{String: "Mock"}, + Cpf: sql.NullString{String: "20815919018"}, + Email: sql.NullString{String: "mock@gmail.com"}, + } It("cadastra cliente com sucesso", func() { - repo.EXPECT().Insere(ctx, clienteDTO).Return(clienteDTO, nil) + repo.EXPECT().PesquisaPorCPF(gomock.Any(), gomock.Any()).Return(nil, nil) + repo.EXPECT().Insere(ctx, gomock.Any()).Return(dto, nil) cli, err := cadastraCliente.Cadastra(ctx, clienteDTO) gomega.Expect(err).To(gomega.BeNil()) gomega.Expect(cli).ToNot(gomega.BeNil()) }) It("falha ao cadastrar cliente", func() { - repo.EXPECT().Insere(ctx, clienteDTO).Return(nil, errors.New("mock error")) + repo.EXPECT().PesquisaPorCPF(gomock.Any(), gomock.Any()).Return(nil, nil) + repo.EXPECT().Insere(ctx, gomock.Any()).Return(nil, errors.New("mock error")) cli, err := cadastraCliente.Cadastra(ctx, clienteDTO) gomega.Expect(err.Error()).To(gomega.Equal("mock error")) diff --git a/internal/core/usecase/cliente_por_cpf.go b/internal/core/usecase/cliente_por_cpf.go index 85f51bc..6bd653d 100644 --- a/internal/core/usecase/cliente_por_cpf.go +++ b/internal/core/usecase/cliente_por_cpf.go @@ -7,7 +7,7 @@ import ( ) type PesquisarCliente interface { - PesquisaPorCPF(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) + PesquisaPorCPF(ctx context.Context, cliente *domain.ClienteRequest) (*domain.Cliente, error) PesquisaPorID(ctx context.Context, id int64) (*domain.Cliente, error) } @@ -21,8 +21,8 @@ func NewPesquisarCliente(clienteRepo repository.ClienteRepo) PesquisarCliente { } } -func (uc *pesquisarClienteUC) PesquisaPorCPF(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) { - return uc.clienteRepo.PesquisaPorCPF(ctx, cliente) +func (uc *pesquisarClienteUC) PesquisaPorCPF(ctx context.Context, cliente *domain.ClienteRequest) (*domain.Cliente, error) { + return uc.clienteRepo.PesquisaPorCPF(ctx, domain.NewClient(cliente)) } func (uc *pesquisarClienteUC) PesquisaPorID(ctx context.Context, id int64) (*domain.Cliente, error) { return uc.clienteRepo.PesquisaPorId(ctx, id) diff --git a/internal/core/usecase/cliente_por_cpf_test.go b/internal/core/usecase/cliente_por_cpf_test.go index 35277e6..d9d3cf3 100644 --- a/internal/core/usecase/cliente_por_cpf_test.go +++ b/internal/core/usecase/cliente_por_cpf_test.go @@ -2,6 +2,7 @@ package usecase import ( "context" + "database/sql" "fiap-tech-challenge-api/internal/core/domain" mock_repo "fiap-tech-challenge-api/test/mock/repository" @@ -24,21 +25,25 @@ var _ = Describe("pesquisa cliente use case testes", func() { }) Context("pesquisa cliente", func() { - clienteDTO := &domain.Cliente{ - Id: 1, + clienteDTO := &domain.ClienteRequest{ Nome: "Mock", Cpf: "20815919018", Email: "mock@gmail.com", } + dto := &domain.Cliente{ + Nome: sql.NullString{String: "Mock"}, + Cpf: sql.NullString{String: "20815919018"}, + Email: sql.NullString{String: "mock@gmail.com"}, + } It("pesquisa por cpf com sucesso", func() { - repo.EXPECT().PesquisaPorCPF(ctx, clienteDTO).Return(clienteDTO, nil) + repo.EXPECT().PesquisaPorCPF(gomock.Any(), gomock.Any()).Return(dto, nil) cli, err := pesquisaPorCpf.PesquisaPorCPF(ctx, clienteDTO) gomega.Expect(err).To(gomega.BeNil()) gomega.Expect(cli).ToNot(gomega.BeNil()) }) It("pesquisa por id com sucesso", func() { - repo.EXPECT().PesquisaPorId(ctx, gomock.Any()).Return(clienteDTO, nil) + repo.EXPECT().PesquisaPorId(ctx, gomock.Any()).Return(dto, nil) cli, err := pesquisaPorCpf.PesquisaPorID(ctx, 1) gomega.Expect(err).To(gomega.BeNil()) diff --git a/internal/core/usecase/lgpd.go b/internal/core/usecase/lgpd.go index 035819d..0097be9 100644 --- a/internal/core/usecase/lgpd.go +++ b/internal/core/usecase/lgpd.go @@ -7,7 +7,7 @@ import ( ) type LGPD interface { - Anonimizar(ctx context.Context, cliente *domain.Cliente) error + Anonimizar(ctx context.Context, cliente *domain.ClienteRequest) error } type anonimizarClienteUC struct { @@ -20,7 +20,18 @@ func NewLGPD(clienteRepo repository.ClienteRepo) LGPD { } } +func (uc *anonimizarClienteUC) Anonimizar(ctx context.Context, cliente *domain.ClienteRequest) error { + dto := domain.NewClient(cliente) -func (uc *anonimizarClienteUC) Anonimizar(ctx context.Context, cliente *domain.Cliente) error { - return uc.clienteRepo.Anonimizar(ctx, cliente) + cli, err := uc.clienteRepo.PesquisaPorCPF(ctx, dto) + if err != nil { + return err + } + + cli.Email.String = "" + cli.Cpf.String = "" + cli.Nome.String = "" + cli.Telefone.String = "" + + return uc.clienteRepo.Anonimizar(ctx, cli) } diff --git a/test/mock/repository/cliente.go b/test/mock/repository/cliente.go index 2e03e23..77a0de6 100644 --- a/test/mock/repository/cliente.go +++ b/test/mock/repository/cliente.go @@ -40,6 +40,20 @@ func (m *MockClienteRepo) EXPECT() *MockClienteRepoMockRecorder { return m.recorder } +// Anonimizar mocks base method. +func (m *MockClienteRepo) Anonimizar(ctx context.Context, cliente *domain.Cliente) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Anonimizar", ctx, cliente) + ret0, _ := ret[0].(error) + return ret0 +} + +// Anonimizar indicates an expected call of Anonimizar. +func (mr *MockClienteRepoMockRecorder) Anonimizar(ctx, cliente any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Anonimizar", reflect.TypeOf((*MockClienteRepo)(nil).Anonimizar), ctx, cliente) +} + // Insere mocks base method. func (m *MockClienteRepo) Insere(ctx context.Context, cliente *domain.Cliente) (*domain.Cliente, error) { m.ctrl.T.Helper()