diff --git a/krakend/krakend.json b/krakend/krakend.json index c4dce3b..297e5a9 100644 --- a/krakend/krakend.json +++ b/krakend/krakend.json @@ -653,6 +653,60 @@ } } }, + { + "endpoint": "/v1/user/{iid}", + "method": "GET", + "input_headers": ["Authorization"], + "output_encoding": "no-op", + "backend": [ + { + "url_pattern": "/v1/user/{iid}", + "host": [ + "http://user-go:8084" + ], + "extra_config": { + "backend/http": { + "return_error_code": true + } + } + } + ], + "extra_config": { + "auth/validator": { + "alg": "HS256", + "jwk_url": "http://fake_api:8080/jwk/symmetric.json", + "roles": ["superadministrator", "administrator"], + "disable_jwk_security": true + } + } + }, + { + "endpoint": "/v1/user/{iid}", + "method": "DELETE", + "input_headers": ["Authorization"], + "output_encoding": "no-op", + "backend": [ + { + "url_pattern": "/v1/user/{iid}", + "host": [ + "http://user-go:8084" + ], + "extra_config": { + "backend/http": { + "return_error_code": true + } + } + } + ], + "extra_config": { + "auth/validator": { + "alg": "HS256", + "jwk_url": "http://fake_api:8080/jwk/symmetric.json", + "roles": ["superadministrator", "administrator"], + "disable_jwk_security": true + } + } + }, { "endpoint": "/v1/user/profile", "method": "GET", diff --git a/services/user/domain/repositories/user.go b/services/user/domain/repositories/user.go index 2e2c60d..15ab701 100644 --- a/services/user/domain/repositories/user.go +++ b/services/user/domain/repositories/user.go @@ -12,4 +12,5 @@ type UserRepository interface { FindByEmail(ctx context.Context, email string) (*entities.User, error) GetAll(ctx context.Context, params *entities.UserQueryParams) ([]*entities.User, int, error) Update(ctx context.Context, payload *entities.User) error + Delete(ctx context.Context, id string) error } diff --git a/services/user/domain/usecases/user.go b/services/user/domain/usecases/user.go index fac67bd..582e568 100644 --- a/services/user/domain/usecases/user.go +++ b/services/user/domain/usecases/user.go @@ -11,6 +11,8 @@ type UserUsecase interface { Create(ctx context.Context, payload entities.UserDto) (*entities.User, *exceptions.CustomError) CreateAuth(ctx context.Context, payload entities.UserDto) (*entities.User, *exceptions.CustomError) GetAll(ctx context.Context, params entities.UserQueryParams) (*entities.UserMeta, *exceptions.CustomError) + Find(ctx context.Context, id string) (*entities.User, *exceptions.CustomError) Update(ctx context.Context, payload entities.UserDto) (*entities.User, *exceptions.CustomError) UpdateEmailVerified(ctx context.Context, payload entities.UserDto) *exceptions.CustomError + Delete(ctx context.Context, id string) *exceptions.CustomError } diff --git a/services/user/internal/delivery/http/delivery/user/delete.go b/services/user/internal/delivery/http/delivery/user/delete.go new file mode 100644 index 0000000..a148da9 --- /dev/null +++ b/services/user/internal/delivery/http/delivery/user/delete.go @@ -0,0 +1,31 @@ +package user_handler + +import ( + "context" + "errors" + "net/http" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + "github.com/febrihidayan/go-architecture-monorepo/pkg/utils" + "github.com/gorilla/mux" +) + +func (x *UserHttpHandler) Delete(w http.ResponseWriter, r *http.Request) { + var ( + ctx = context.Background() + vars = mux.Vars(r) + id = vars["id"] + ) + + if id == "" { + utils.RespondWithError(w, http.StatusBadRequest, []error{errors.New("param id required")}) + return + } + + if err := x.UserUsecase.Delete(ctx, id); err != nil { + utils.RespondWithError(w, exceptions.MapToHttpStatusCode(err.Status), err.Errors.Errors) + return + } + + utils.RespondWithJSON(w, http.StatusOK, nil) +} diff --git a/services/user/internal/delivery/http/delivery/user/find.go b/services/user/internal/delivery/http/delivery/user/find.go new file mode 100644 index 0000000..b130590 --- /dev/null +++ b/services/user/internal/delivery/http/delivery/user/find.go @@ -0,0 +1,33 @@ +package user_handler + +import ( + "context" + "errors" + "net/http" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + "github.com/febrihidayan/go-architecture-monorepo/pkg/utils" + "github.com/febrihidayan/go-architecture-monorepo/services/user/internal/delivery/http/response" + "github.com/gorilla/mux" +) + +func (x *UserHttpHandler) Find(w http.ResponseWriter, r *http.Request) { + var ( + ctx = context.Background() + vars = mux.Vars(r) + id = vars["id"] + ) + + if id == "" { + utils.RespondWithError(w, http.StatusBadRequest, []error{errors.New("param id required")}) + return + } + + result, err := x.UserUsecase.Find(ctx, id) + if err != nil { + utils.RespondWithError(w, exceptions.MapToHttpStatusCode(err.Status), err.Errors.Errors) + return + } + + utils.RespondWithJSON(w, http.StatusOK, response.MapUserListResponse(result)) +} diff --git a/services/user/internal/delivery/http/delivery/user/handler.go b/services/user/internal/delivery/http/delivery/user/handler.go index dd713fd..43762f4 100644 --- a/services/user/internal/delivery/http/delivery/user/handler.go +++ b/services/user/internal/delivery/http/delivery/user/handler.go @@ -31,5 +31,7 @@ func NewUserHttpHandler( r.HandleFunc("/v1/users", handler.GetAll).Methods("GET") r.HandleFunc("/v1/user", handler.Create).Methods("POST") + r.HandleFunc("/v1/user/{id}", handler.Find).Methods("GET") r.HandleFunc("/v1/user/{id}", handler.Update).Methods("PUT") + r.HandleFunc("/v1/user/{id}", handler.Delete).Methods("DELETE") } diff --git a/services/user/internal/repositories/mongo/user_repository.go b/services/user/internal/repositories/mongo/user_repository.go index 5c606ad..4f7a3e5 100644 --- a/services/user/internal/repositories/mongo/user_repository.go +++ b/services/user/internal/repositories/mongo/user_repository.go @@ -98,3 +98,15 @@ func (x *UserRepository) Update(ctx context.Context, payload *entities.User) err return nil } + +func (x *UserRepository) Delete(ctx context.Context, id string) error { + _, err := x.db.DeleteOne(ctx, bson.M{ + "_id": id, + }) + + if err != nil { + return err + } + + return nil +} diff --git a/services/user/internal/usecases/user/delete.go b/services/user/internal/usecases/user/delete.go new file mode 100644 index 0000000..9a5e494 --- /dev/null +++ b/services/user/internal/usecases/user/delete.go @@ -0,0 +1,23 @@ +package user + +import ( + "context" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + + "github.com/hashicorp/go-multierror" +) + +func (x *userInteractor) Delete(ctx context.Context, id string) *exceptions.CustomError { + var multilerr *multierror.Error + + if err := x.userRepo.Delete(ctx, id); err != nil { + multilerr = multierror.Append(multilerr, err) + return &exceptions.CustomError{ + Status: exceptions.ERRREPOSITORY, + Errors: multilerr, + } + } + + return nil +} diff --git a/services/user/internal/usecases/user/find.go b/services/user/internal/usecases/user/find.go new file mode 100644 index 0000000..156c498 --- /dev/null +++ b/services/user/internal/usecases/user/find.go @@ -0,0 +1,25 @@ +package user + +import ( + "context" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + "github.com/febrihidayan/go-architecture-monorepo/services/user/domain/entities" + + "github.com/hashicorp/go-multierror" +) + +func (x *userInteractor) Find(ctx context.Context, id string) (*entities.User, *exceptions.CustomError) { + var multilerr *multierror.Error + + find, err := x.userRepo.Find(ctx, id) + if err != nil { + multilerr = multierror.Append(multilerr, err) + return nil, &exceptions.CustomError{ + Status: exceptions.ERRREPOSITORY, + Errors: multilerr, + } + } + + return find, nil +} diff --git a/services/user/tests/internal/delivery/http/user/delete_test.go b/services/user/tests/internal/delivery/http/user/delete_test.go new file mode 100644 index 0000000..fa81eab --- /dev/null +++ b/services/user/tests/internal/delivery/http/user/delete_test.go @@ -0,0 +1,65 @@ +package user + +import ( + "net/http" + "net/http/httptest" + + "github.com/gorilla/mux" + "github.com/stretchr/testify/mock" +) + +func (x *UserHandlerSuite) TestDelete() { + testCases := []struct { + name string + param HandlerParams + mock func(m HandlerParams) + }{ + { + name: "Success Positive Case", + param: HandlerParams{ + method: http.MethodPost, + path: "/v1/user/{id}", + payload: Any{ + x.Token, + x.Id.String(), + }, + expected: 200, + }, + mock: func(m HandlerParams) { + x.UserUsecase.On("Delete", mock.Anything, mock.Anything).Return(nil) + }, + }, + { + name: "Failed Negatif Case", + param: HandlerParams{ + method: http.MethodPost, + path: "/v1/user/{id}", + payload: Any{ + x.Token, + "", + }, + expected: 400, + }, + mock: func(m HandlerParams) { + x.UserUsecase.On("Delete", mock.Anything, mock.Anything).Return(x.Error) + }, + }, + } + + for _, tc := range testCases { + x.Run(tc.name, func() { + x.SetupTest() + + token := tc.param.payload.Get(0).(string) + id := tc.param.payload.Get(1).(string) + + req := httptest.NewRequest(tc.param.method, tc.param.path, nil) + req.Header.Set("Authorization", token) + re := mux.SetURLVars(req, map[string]string{"id": id}) + + tc.mock(tc.param) + http.HandlerFunc(x.Http.Delete).ServeHTTP(x.Response, re) + x.Equal(tc.param.expected, x.Response.Result().StatusCode) + }) + } +} diff --git a/services/user/tests/internal/delivery/http/user/find_test.go b/services/user/tests/internal/delivery/http/user/find_test.go new file mode 100644 index 0000000..8d2e122 --- /dev/null +++ b/services/user/tests/internal/delivery/http/user/find_test.go @@ -0,0 +1,66 @@ +package user + +import ( + "net/http" + "net/http/httptest" + + "github.com/febrihidayan/go-architecture-monorepo/services/user/domain/entities" + "github.com/gorilla/mux" + "github.com/stretchr/testify/mock" +) + +func (x *UserHandlerSuite) TestFind() { + testCases := []struct { + name string + param HandlerParams + mock func(m HandlerParams) + }{ + { + name: "Success Positive Case", + param: HandlerParams{ + method: http.MethodPost, + path: "/v1/user/{id}", + payload: Any{ + x.Token, + x.Id.String(), + }, + expected: 200, + }, + mock: func(m HandlerParams) { + x.UserUsecase.On("Find", mock.Anything, mock.Anything).Return(&entities.User{}, nil) + }, + }, + { + name: "Failed Negatif Case", + param: HandlerParams{ + method: http.MethodPost, + path: "/v1/user/{id}", + payload: Any{ + x.Token, + "", + }, + expected: 400, + }, + mock: func(m HandlerParams) { + x.UserUsecase.On("Find", mock.Anything, mock.Anything).Return(nil, x.Error) + }, + }, + } + + for _, tc := range testCases { + x.Run(tc.name, func() { + x.SetupTest() + + token := tc.param.payload.Get(0).(string) + id := tc.param.payload.Get(1).(string) + + req := httptest.NewRequest(tc.param.method, tc.param.path, nil) + req.Header.Set("Authorization", token) + re := mux.SetURLVars(req, map[string]string{"id": id}) + + tc.mock(tc.param) + http.HandlerFunc(x.Http.Find).ServeHTTP(x.Response, re) + x.Equal(tc.param.expected, x.Response.Result().StatusCode) + }) + } +} diff --git a/services/user/tests/internal/usecases/user/delete_test.go b/services/user/tests/internal/usecases/user/delete_test.go new file mode 100644 index 0000000..0cb0b14 --- /dev/null +++ b/services/user/tests/internal/usecases/user/delete_test.go @@ -0,0 +1,53 @@ +package user + +import ( + "context" + "errors" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/common" + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + "github.com/hashicorp/go-multierror" + "github.com/stretchr/testify/mock" +) + +func (x *UserUsecaseSuite) TestDelete() { + id := common.NewID() + + args := []struct { + name string + args Any + tests func(arg Any) + }{ + { + name: "Success Positive Case", + tests: func(arg Any) { + x.userRepo.Mock.On("Delete", id.String()).Return(nil) + + err := x.userUsecase.Delete(context.Background(), id.String()) + x.Nil(err) + }, + }, + { + name: "Failed Negative Case", + tests: func(arg Any) { + x.userRepo.Mock.On("Delete", id.String()).Return(errors.New(mock.Anything)) + + err := x.userUsecase.Delete(context.Background(), id.String()) + + e := &exceptions.CustomError{ + Status: exceptions.ERRREPOSITORY, + Errors: multierror.Append(errors.New(mock.Anything)), + } + + x.Equal(err, e) + }, + }, + } + + for _, arg := range args { + x.Run(arg.name, func() { + x.SetupTest() + arg.tests(arg.args) + }) + } +} diff --git a/services/user/tests/internal/usecases/user/find_test.go b/services/user/tests/internal/usecases/user/find_test.go new file mode 100644 index 0000000..e7ff89c --- /dev/null +++ b/services/user/tests/internal/usecases/user/find_test.go @@ -0,0 +1,65 @@ +package user + +import ( + "context" + "errors" + + "github.com/febrihidayan/go-architecture-monorepo/pkg/common" + "github.com/febrihidayan/go-architecture-monorepo/pkg/exceptions" + "github.com/febrihidayan/go-architecture-monorepo/pkg/utils" + "github.com/febrihidayan/go-architecture-monorepo/services/user/domain/entities" + "github.com/hashicorp/go-multierror" + "github.com/stretchr/testify/mock" +) + +func (x *UserUsecaseSuite) TestFind() { + id := common.NewID() + var user *entities.User + + user = &entities.User{ + ID: id, + Name: "Admin", + Email: "admin@app.com", + CreatedAt: utils.TimeUTC(), + UpdatedAt: utils.TimeUTC(), + } + + args := []struct { + name string + args Any + tests func(arg Any) + }{ + { + name: "Success Positive Case", + tests: func(arg Any) { + x.userRepo.Mock.On("Find", id.String()).Return(user, nil) + + find, err := x.userUsecase.Find(context.Background(), id.String()) + x.Nil(err) + x.Equal(find, user) + }, + }, + { + name: "Failed Negative Case", + tests: func(arg Any) { + x.userRepo.Mock.On("Find", id.String()).Return(nil, errors.New(mock.Anything)) + + _, err := x.userUsecase.Find(context.Background(), id.String()) + + e := &exceptions.CustomError{ + Status: exceptions.ERRREPOSITORY, + Errors: multierror.Append(errors.New(mock.Anything)), + } + + x.Equal(err, e) + }, + }, + } + + for _, arg := range args { + x.Run(arg.name, func() { + x.SetupTest() + arg.tests(arg.args) + }) + } +} diff --git a/services/user/tests/mocks/repositories/mongo/user_repository_mock.go b/services/user/tests/mocks/repositories/mongo/user_repository_mock.go index 5223fea..1c14b5c 100644 --- a/services/user/tests/mocks/repositories/mongo/user_repository_mock.go +++ b/services/user/tests/mocks/repositories/mongo/user_repository_mock.go @@ -76,3 +76,13 @@ func (x *UserRepositoryMock) Update(ctx context.Context, payload *entities.User) return } + +func (x *UserRepositoryMock) Delete(ctx context.Context, id string) (err error) { + args := x.Called(id) + + if n, ok := args.Get(0).(error); ok { + err = n + } + + return +} diff --git a/services/user/tests/mocks/usecases/user_mock.go b/services/user/tests/mocks/usecases/user_mock.go index 24933fc..6d63eb6 100644 --- a/services/user/tests/mocks/usecases/user_mock.go +++ b/services/user/tests/mocks/usecases/user_mock.go @@ -40,6 +40,20 @@ func (x *UserUsecaseMock) CreateAuth(ctx context.Context, payload entities.UserD return } +func (x *UserUsecaseMock) Find(ctx context.Context, id string) (result *entities.User, err *exceptions.CustomError) { + args := x.Called(id) + + if n, ok := args.Get(0).(*entities.User); ok { + result = n + } + + if n, ok := args.Get(1).(*exceptions.CustomError); ok { + err = n + } + + return +} + func (x *UserUsecaseMock) GetAll(ctx context.Context, params entities.UserQueryParams) (result *entities.UserMeta, err *exceptions.CustomError) { args := x.Called(params) @@ -77,3 +91,13 @@ func (x *UserUsecaseMock) UpdateEmailVerified(ctx context.Context, payload entit return } + +func (x *UserUsecaseMock) Delete(ctx context.Context, id string) (err *exceptions.CustomError) { + args := x.Called(id) + + if n, ok := args.Get(0).(*exceptions.CustomError); ok { + err = n + } + + return +}