diff --git a/README.md b/README.md index 64ac4d5..568e212 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ If You using my Template. You get some endpoints that I have set up and an archi https://documenter.getpostman.com/view/29665461/2s9YJaZQCG -![image](https://github.com/Caknoooo/go-gin-clean-template/assets/92671053/5aea055b-2420-4017-9310-e1c628209d0d) +![image](https://github.com/Caknoooo/go-gin-clean-architecture/assets/92671053/ad3e680c-4b4f-4c31-a70c-497e0a586812) You will get an issue template as below diff --git a/controller/user_controller.go b/controller/user_controller.go index 5ccf938..8749759 100644 --- a/controller/user_controller.go +++ b/controller/user_controller.go @@ -4,31 +4,30 @@ import ( "net/http" "github.com/Caknoooo/go-gin-clean-template/dto" - "github.com/Caknoooo/go-gin-clean-template/entity" "github.com/Caknoooo/go-gin-clean-template/service" "github.com/Caknoooo/go-gin-clean-template/utils" "github.com/gin-gonic/gin" ) -type UserController interface { - Register(ctx *gin.Context) - GetAllUser(ctx *gin.Context) - Me(ctx *gin.Context) - SendVerificationEmail(ctx *gin.Context) - VerifyEmail(ctx *gin.Context) - Login(ctx *gin.Context) - Update(ctx *gin.Context) - Delete(ctx *gin.Context) -} +type ( + UserController interface { + Register(ctx *gin.Context) + Login(ctx *gin.Context) + Me(ctx *gin.Context) + GetAllUser(ctx *gin.Context) + SendVerificationEmail(ctx *gin.Context) + VerifyEmail(ctx *gin.Context) + Update(ctx *gin.Context) + Delete(ctx *gin.Context) + } -type userController struct { - jwtService service.JWTService - userService service.UserService -} + userController struct { + userService service.UserService + } +) -func NewUserController(us service.UserService, jwt service.JWTService) UserController { +func NewUserController(us service.UserService) UserController { return &userController{ - jwtService: jwt, userService: us, } } @@ -67,13 +66,13 @@ func (c *userController) GetAllUser(ctx *gin.Context) { return } - resp := utils.Response { - Status: true, + resp := utils.Response{ + Status: true, Message: dto.MESSAGE_SUCCESS_GET_LIST_USER, - Data: result.Data, - Meta: result.PaginationResponse, + Data: result.Data, + Meta: result.PaginationResponse, } - + ctx.JSON(http.StatusOK, resp) } @@ -99,28 +98,15 @@ func (c *userController) Login(ctx *gin.Context) { return } - res, err := c.userService.Verify(ctx.Request.Context(), req.Email, req.Password) - if err != nil && !res { - response := utils.BuildResponseFailed(dto.MESSAGE_FAILED_LOGIN, err.Error(), nil) - ctx.AbortWithStatusJSON(http.StatusUnauthorized, response) - return - } - - user, err := c.userService.GetUserByEmail(ctx.Request.Context(), req.Email) + result, err := c.userService.Verify(ctx.Request.Context(), req) if err != nil { - response := utils.BuildResponseFailed(dto.MESSAGE_FAILED_LOGIN, err.Error(), nil) - ctx.AbortWithStatusJSON(http.StatusBadRequest, response) + res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_LOGIN, err.Error(), nil) + ctx.JSON(http.StatusBadRequest, res) return } - token := c.jwtService.GenerateToken(user.ID, user.Role) - userResponse := entity.Authorization{ - Token: token, - Role: user.Role, - } - - response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_LOGIN, userResponse) - ctx.JSON(http.StatusOK, response) + res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_LOGIN, result) + ctx.JSON(http.StatusOK, res) } func (c *userController) SendVerificationEmail(ctx *gin.Context) { diff --git a/dto/user_dto.go b/dto/user_dto.go index 625ac0e..40594c5 100644 --- a/dto/user_dto.go +++ b/dto/user_dto.go @@ -81,22 +81,21 @@ type ( GetAllUserRepositoryResponse struct { Users []entity.User PaginationResponse - } + } UserUpdateRequest struct { Name string `json:"name" form:"name"` TelpNumber string `json:"telp_number" form:"telp_number"` Email string `json:"email" form:"email"` - Password string `json:"password" form:"password"` } UserUpdateResponse struct { ID string `json:"id"` - Name string `json:"name,omitempty"` - TelpNumber string `json:"telp_number,omitempty"` - Role string `json:"role,omitempty"` - Email string `json:"email,omitempty"` - IsVerified bool `json:"is_verified,omitempty"` + Name string `json:"name"` + TelpNumber string `json:"telp_number"` + Role string `json:"role"` + Email string `json:"email"` + IsVerified bool `json:"is_verified"` } SendVerificationEmailRequest struct { @@ -117,6 +116,11 @@ type ( Password string `json:"password" form:"password" binding:"required"` } + UserLoginResponse struct { + Token string `json:"token"` + Role string `json:"role"` + } + UpdateStatusIsVerifiedRequest struct { UserId string `json:"user_id" form:"user_id" binding:"required"` IsVerified bool `json:"is_verified" form:"is_verified"` diff --git a/main.go b/main.go index 5720abf..d8c1d39 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,8 @@ func main() { db *gorm.DB = config.SetUpDatabaseConnection() jwtService service.JWTService = service.NewJWTService() userRepository repository.UserRepository = repository.NewUserRepository(db) - userService service.UserService = service.NewUserService(userRepository) - userController controller.UserController = controller.NewUserController(userService, jwtService) + userService service.UserService = service.NewUserService(userRepository, jwtService) + userController controller.UserController = controller.NewUserController(userService) ) server := gin.Default() diff --git a/repository/user_repository.go b/repository/user_repository.go index c7e62db..e0e636b 100644 --- a/repository/user_repository.go +++ b/repository/user_repository.go @@ -9,19 +9,21 @@ import ( "gorm.io/gorm" ) -type UserRepository interface { - RegisterUser(ctx context.Context, user entity.User) (entity.User, error) - GetAllUserWithPagination(ctx context.Context, tx *gorm.DB, req dto.PaginationRequest) (dto.GetAllUserRepositoryResponse, error) - GetUserById(ctx context.Context, userId string) (entity.User, error) - GetUserByEmail(ctx context.Context, email string) (entity.User, error) - CheckEmail(ctx context.Context, email string) (bool, error) - UpdateUser(ctx context.Context, user entity.User) (entity.User, error) - DeleteUser(ctx context.Context, userId string) error -} +type ( + UserRepository interface { + RegisterUser(ctx context.Context, tx *gorm.DB, user entity.User) (entity.User, error) + GetAllUserWithPagination(ctx context.Context, tx *gorm.DB, req dto.PaginationRequest) (dto.GetAllUserRepositoryResponse, error) + GetUserById(ctx context.Context, tx *gorm.DB, userId string) (entity.User, error) + GetUserByEmail(ctx context.Context, tx *gorm.DB, email string) (entity.User, error) + CheckEmail(ctx context.Context, tx *gorm.DB, email string) (entity.User, bool, error) + UpdateUser(ctx context.Context, tx *gorm.DB, user entity.User) (entity.User, error) + DeleteUser(ctx context.Context, tx *gorm.DB, userId string) error + } -type userRepository struct { - db *gorm.DB -} + userRepository struct { + db *gorm.DB + } +) func NewUserRepository(db *gorm.DB) UserRepository { return &userRepository{ @@ -29,10 +31,15 @@ func NewUserRepository(db *gorm.DB) UserRepository { } } -func (r *userRepository) RegisterUser(ctx context.Context, user entity.User) (entity.User, error) { - if err := r.db.Create(&user).Error; err != nil { +func (r *userRepository) RegisterUser(ctx context.Context, tx *gorm.DB, user entity.User) (entity.User, error) { + if tx == nil { + tx = r.db + } + + if err := tx.WithContext(ctx).Create(&user).Error; err != nil { return entity.User{}, err } + return user, nil } @@ -53,23 +60,23 @@ func (r *userRepository) GetAllUserWithPagination(ctx context.Context, tx *gorm. req.Page = 1 } + query := tx.WithContext(ctx).Model(&entity.User{}) if req.Search != "" { - err = tx.WithContext(ctx).Model(&entity.User{}).Where("name ILIKE ?", "%"+req.Search+"%").Count(&count).Error - if err != nil { - return dto.GetAllUserRepositoryResponse{}, err - } - } else { - err = tx.WithContext(ctx).Model(&entity.User{}).Count(&count).Error - if err != nil { - return dto.GetAllUserRepositoryResponse{}, err - } - } - - stmt := tx.WithContext(ctx).Where("name ILIKE ?", "%"+req.Search+"%") - maxPage := int64(math.Ceil(float64(count) / float64(req.PerPage))) + query = query.Where("name LIKE ?", "%"+req.Search+"%") + } + + err = query.Count(&count).Error + if err != nil { + return dto.GetAllUserRepositoryResponse{}, err + } offset := (req.Page - 1) * req.PerPage - _ = stmt.Offset(offset).Limit(req.PerPage).Find(&users).Error + maxPage := int64(math.Ceil(float64(count) / float64(req.PerPage))) + + err = query.Offset(offset).Limit(req.PerPage).Find(&users).Error + if err != nil { + return dto.GetAllUserRepositoryResponse{}, err + } return dto.GetAllUserRepositoryResponse{ Users: users, @@ -82,40 +89,65 @@ func (r *userRepository) GetAllUserWithPagination(ctx context.Context, tx *gorm. }, nil } -func (r *userRepository) GetUserById(ctx context.Context, userId string) (entity.User, error) { +func (r *userRepository) GetUserById(ctx context.Context, tx *gorm.DB, userId string) (entity.User, error) { + if tx == nil { + tx = r.db + } + var user entity.User - if err := r.db.Where("id = ?", userId).Take(&user).Error; err != nil { + if err := tx.WithContext(ctx).Where("id = ?", userId).Take(&user).Error; err != nil { return entity.User{}, err } + return user, nil } -func (r *userRepository) GetUserByEmail(ctx context.Context, email string) (entity.User, error) { +func (r *userRepository) GetUserByEmail(ctx context.Context, tx *gorm.DB, email string) (entity.User, error) { + if tx == nil { + tx = r.db + } + var user entity.User - if err := r.db.Where("email = ?", email).Take(&user).Error; err != nil { + if err := tx.WithContext(ctx).Where("email = ?", email).Take(&user).Error; err != nil { return entity.User{}, err } + return user, nil } -func (r *userRepository) CheckEmail(ctx context.Context, email string) (bool, error) { +func (r *userRepository) CheckEmail(ctx context.Context, tx *gorm.DB, email string) (entity.User, bool, error) { + if tx == nil { + tx = r.db + } + var user entity.User - if err := r.db.Where("email = ?", email).Take(&user).Error; err != nil { - return false, err + if err := tx.WithContext(ctx).Where("email = ?", email).Take(&user).Error; err != nil { + return entity.User{}, false, err } - return true, nil + + return user, true, nil } -func (r *userRepository) UpdateUser(ctx context.Context, user entity.User) (entity.User, error) { - if err := r.db.Updates(&user).Error; err != nil { +func (r *userRepository) UpdateUser(ctx context.Context, tx *gorm.DB, user entity.User) (entity.User, error) { + if tx == nil { + tx = r.db + } + + if err := tx.WithContext(ctx).Updates(&user).Error; err != nil { return entity.User{}, err } + return user, nil } -func (r *userRepository) DeleteUser(ctx context.Context, userId string) error { - if err := r.db.Delete(&entity.User{}, &userId).Error; err != nil { +func (r *userRepository) DeleteUser(ctx context.Context, tx *gorm.DB, userId string) error { + if tx == nil { + tx = r.db + } + + if err := tx.WithContext(ctx).Delete(&entity.User{}, "id = ?", userId).Error; err != nil { return err } + return nil } diff --git a/routes/user_route.go b/routes/user_route.go index e300495..87c9b88 100644 --- a/routes/user_route.go +++ b/routes/user_route.go @@ -17,7 +17,7 @@ func User(route *gin.Engine, userController controller.UserController, jwtServic routes.DELETE("", middleware.Authenticate(jwtService), userController.Delete) routes.PATCH("", middleware.Authenticate(jwtService), userController.Update) routes.GET("/me", middleware.Authenticate(jwtService), userController.Me) - routes.POST("/verify-email", userController.VerifyEmail) - routes.POST("/verification-email", userController.SendVerificationEmail) + routes.POST("/verify_email", userController.VerifyEmail) + routes.POST("/send_verification_email", userController.SendVerificationEmail) } } diff --git a/service/user_service.go b/service/user_service.go index aebb6d1..2cc2f3c 100644 --- a/service/user_service.go +++ b/service/user_service.go @@ -18,17 +18,30 @@ import ( "github.com/google/uuid" ) -type UserService interface { - RegisterUser(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error) - GetAllUserWithPagination(ctx context.Context, req dto.PaginationRequest) (dto.UserPaginationResponse, error) - GetUserById(ctx context.Context, userId string) (dto.UserResponse, error) - GetUserByEmail(ctx context.Context, email string) (dto.UserResponse, error) - SendVerificationEmail(ctx context.Context, req dto.SendVerificationEmailRequest) error - VerifyEmail(ctx context.Context, req dto.VerifyEmailRequest) (dto.VerifyEmailResponse, error) - CheckUser(ctx context.Context, email string) (bool, error) - UpdateUser(ctx context.Context, req dto.UserUpdateRequest, userId string) (dto.UserUpdateResponse, error) - DeleteUser(ctx context.Context, userId string) error - Verify(ctx context.Context, email string, password string) (bool, error) +type ( + UserService interface { + RegisterUser(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error) + GetAllUserWithPagination(ctx context.Context, req dto.PaginationRequest) (dto.UserPaginationResponse, error) + GetUserById(ctx context.Context, userId string) (dto.UserResponse, error) + GetUserByEmail(ctx context.Context, email string) (dto.UserResponse, error) + SendVerificationEmail(ctx context.Context, req dto.SendVerificationEmailRequest) error + VerifyEmail(ctx context.Context, req dto.VerifyEmailRequest) (dto.VerifyEmailResponse, error) + UpdateUser(ctx context.Context, req dto.UserUpdateRequest, userId string) (dto.UserUpdateResponse, error) + DeleteUser(ctx context.Context, userId string) error + Verify(ctx context.Context, req dto.UserLoginRequest) (dto.UserLoginResponse, error) + } + + userService struct { + userRepo repository.UserRepository + jwtService JWTService + } +) + +func NewUserService(userRepo repository.UserRepository, jwtService JWTService) UserService { + return &userService{ + userRepo: userRepo, + jwtService: jwtService, + } } const ( @@ -36,19 +49,9 @@ const ( VERIFY_EMAIL_ROUTE = "register/verify_email" ) -type userService struct { - userRepo repository.UserRepository -} - -func NewUserService(ur repository.UserRepository) UserService { - return &userService{ - userRepo: ur, - } -} - func (s *userService) RegisterUser(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error) { - email, _ := s.userRepo.CheckEmail(ctx, req.Email) - if email { + _, flag, _ := s.userRepo.CheckEmail(ctx, nil, req.Email) + if flag { return dto.UserResponse{}, dto.ErrEmailAlreadyExists } @@ -70,7 +73,7 @@ func (s *userService) RegisterUser(ctx context.Context, req dto.UserCreateReques IsVerified: false, } - userReg, err := s.userRepo.RegisterUser(ctx, user) + userReg, err := s.userRepo.RegisterUser(ctx, nil, user) if err != nil { return dto.UserResponse{}, dto.ErrCreateUser } @@ -138,7 +141,7 @@ func makeVerificationEmail(receiverEmail string) (map[string]string, error) { } func (s *userService) SendVerificationEmail(ctx context.Context, req dto.SendVerificationEmailRequest) error { - user, err := s.userRepo.GetUserByEmail(ctx, req.Email) + user, err := s.userRepo.GetUserByEmail(ctx, nil, req.Email) if err != nil { return dto.ErrEmailNotFound } @@ -183,7 +186,7 @@ func (s *userService) VerifyEmail(ctx context.Context, req dto.VerifyEmailReques }, dto.ErrTokenExpired } - user, err := s.userRepo.GetUserByEmail(ctx, email) + user, err := s.userRepo.GetUserByEmail(ctx, nil, email) if err != nil { return dto.VerifyEmailResponse{}, dto.ErrUserNotFound } @@ -192,7 +195,7 @@ func (s *userService) VerifyEmail(ctx context.Context, req dto.VerifyEmailReques return dto.VerifyEmailResponse{}, dto.ErrAccountAlreadyVerified } - updatedUser, err := s.userRepo.UpdateUser(ctx, entity.User{ + updatedUser, err := s.userRepo.UpdateUser(ctx, nil, entity.User{ ID: user.ID, IsVerified: true, }) @@ -220,6 +223,7 @@ func (s *userService) GetAllUserWithPagination(ctx context.Context, req dto.Pagi Email: user.Email, Role: user.Role, TelpNumber: user.TelpNumber, + ImageUrl: user.ImageUrl, IsVerified: user.IsVerified, } @@ -238,7 +242,7 @@ func (s *userService) GetAllUserWithPagination(ctx context.Context, req dto.Pagi } func (s *userService) GetUserById(ctx context.Context, userId string) (dto.UserResponse, error) { - user, err := s.userRepo.GetUserById(ctx, userId) + user, err := s.userRepo.GetUserById(ctx, nil, userId) if err != nil { return dto.UserResponse{}, dto.ErrGetUserById } @@ -249,12 +253,13 @@ func (s *userService) GetUserById(ctx context.Context, userId string) (dto.UserR TelpNumber: user.TelpNumber, Role: user.Role, Email: user.Email, + ImageUrl: user.ImageUrl, IsVerified: user.IsVerified, }, nil } func (s *userService) GetUserByEmail(ctx context.Context, email string) (dto.UserResponse, error) { - emails, err := s.userRepo.GetUserByEmail(ctx, email) + emails, err := s.userRepo.GetUserByEmail(ctx, nil, email) if err != nil { return dto.UserResponse{}, dto.ErrGetUserByEmail } @@ -265,24 +270,13 @@ func (s *userService) GetUserByEmail(ctx context.Context, email string) (dto.Use TelpNumber: emails.TelpNumber, Role: emails.Role, Email: emails.Email, + ImageUrl: emails.ImageUrl, IsVerified: emails.IsVerified, }, nil } -func (s *userService) CheckUser(ctx context.Context, email string) (bool, error) { - res, err := s.userRepo.GetUserByEmail(ctx, email) - if err != nil { - return false, err - } - - if res.Email == "" { - return false, err - } - return true, nil -} - func (s *userService) UpdateUser(ctx context.Context, req dto.UserUpdateRequest, userId string) (dto.UserUpdateResponse, error) { - user, err := s.userRepo.GetUserById(ctx, userId) + user, err := s.userRepo.GetUserById(ctx, nil, userId) if err != nil { return dto.UserUpdateResponse{}, dto.ErrUserNotFound } @@ -293,10 +287,9 @@ func (s *userService) UpdateUser(ctx context.Context, req dto.UserUpdateRequest, TelpNumber: req.TelpNumber, Role: user.Role, Email: req.Email, - Password: req.Password, } - userUpdate, err := s.userRepo.UpdateUser(ctx, data) + userUpdate, err := s.userRepo.UpdateUser(ctx, nil, data) if err != nil { return dto.UserUpdateResponse{}, dto.ErrUpdateUser } @@ -307,17 +300,17 @@ func (s *userService) UpdateUser(ctx context.Context, req dto.UserUpdateRequest, TelpNumber: userUpdate.TelpNumber, Role: userUpdate.Role, Email: userUpdate.Email, - IsVerified: userUpdate.IsVerified, + IsVerified: user.IsVerified, }, nil } func (s *userService) DeleteUser(ctx context.Context, userId string) error { - user, err := s.userRepo.GetUserById(ctx, userId) + user, err := s.userRepo.GetUserById(ctx, nil, userId) if err != nil { return dto.ErrUserNotFound } - err = s.userRepo.DeleteUser(ctx, user.ID.String()) + err = s.userRepo.DeleteUser(ctx, nil, user.ID.String()) if err != nil { return dto.ErrDeleteUser } @@ -325,24 +318,25 @@ func (s *userService) DeleteUser(ctx context.Context, userId string) error { return nil } -func (s *userService) Verify(ctx context.Context, email string, password string) (bool, error) { - res, err := s.userRepo.GetUserByEmail(ctx, email) - if err != nil { - return false, dto.ErrUserNotFound +func (s *userService) Verify(ctx context.Context, req dto.UserLoginRequest) (dto.UserLoginResponse, error) { + check, flag, err := s.userRepo.CheckEmail(ctx, nil, req.Email) + if err != nil || !flag { + return dto.UserLoginResponse{}, dto.ErrEmailNotFound } - if !res.IsVerified { - return false, dto.ErrAccountNotVerified + if !check.IsVerified { + return dto.UserLoginResponse{}, dto.ErrAccountNotVerified } - checkPassword, err := helpers.CheckPassword(res.Password, []byte(password)) - if err != nil { - return false, dto.ErrPasswordNotMatch + checkPassword, err := helpers.CheckPassword(check.Password, []byte(req.Password)) + if err != nil || !checkPassword { + return dto.UserLoginResponse{}, dto.ErrPasswordNotMatch } - if res.Email == email && checkPassword { - return true, nil - } + token := s.jwtService.GenerateToken(check.ID.String(), check.Role) - return false, dto.ErrEmailOrPassword + return dto.UserLoginResponse{ + Token: token, + Role: check.Role, + }, nil } diff --git a/tests/user_test.go b/tests/user_test.go index 85fc326..6737edd 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -22,9 +22,9 @@ func SetupControllerUser() controller.UserController { var ( db = SetUpDatabaseConnection() userRepo = repository.NewUserRepository(db) - userService = service.NewUserService(userRepo) jwtService = service.NewJWTService() - userController = controller.NewUserController(userService, jwtService) + userService = service.NewUserService(userRepo, jwtService) + userController = controller.NewUserController(userService) ) return userController