From f48d61d4e23fefb0f63d146ac1bbc9e7dd81f9d7 Mon Sep 17 00:00:00 2001 From: areeqakbr Date: Sun, 22 Jun 2025 17:19:18 +0700 Subject: [PATCH] fixing validation for username and full name --- model/dto/user_dto.go | 8 ++--- model/entity/users.go | 6 ++-- repository/users_repo.go | 16 ++++++--- usecase/users_usecase.go | 26 ++++++++++---- utils/validation/validation_username.go | 48 +++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 utils/validation/validation_username.go diff --git a/model/dto/user_dto.go b/model/dto/user_dto.go index 947e335..2a572e4 100644 --- a/model/dto/user_dto.go +++ b/model/dto/user_dto.go @@ -16,10 +16,10 @@ const ( ) type UserRegisterDTO struct { - Name string `json:"name" validate:"required"` - Username string `json:"username" validate:"required"` - Password string `json:"password" validate:"required,min=6"` - NomorInduk string `json:"nomor_induk" validate:"required"` + Name string `json:"name" validate:"required,min=2,max=100,alphaspace"` + Username string `json:"username" validate:"required,min=3,max=50,alphanumunderscore,nospace"` + Password string `json:"password" validate:"required,min=6,max=100"` + NomorInduk string `json:"nomor_induk" validate:"required,min=3,max=50,alphanum"` } type UserLoginDTO struct { diff --git a/model/entity/users.go b/model/entity/users.go index 39c45ca..c0a2798 100644 --- a/model/entity/users.go +++ b/model/entity/users.go @@ -16,13 +16,13 @@ const ( type User struct { ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` - NomorInduk *string `json:"nomor_induk,omitempty" gorm:"unique"` // Add this field + NomorInduk *string `json:"nomor_induk,omitempty" gorm:"unique"` RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"` Role Role `json:"role" gorm:"foreignKey:RoleID"` - Name string `json:"name"` + Name string `json:"name" gorm:"unique"` Username string `json:"username" gorm:"unique"` Password string `json:"password"` - Status UserStatus `json:"status" gorm:"type:varchar(20);default:'pending'"` // Add status field + Status UserStatus `json:"status" gorm:"type:varchar(20);default:'pending'"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/repository/users_repo.go b/repository/users_repo.go index cb1258c..3ee1aed 100644 --- a/repository/users_repo.go +++ b/repository/users_repo.go @@ -11,10 +11,10 @@ type UsersRepo interface { Post(user entity.User) error GetRoleByDepartment(departmentName string) (entity.Role, error) GetUserByUsername(username string) (entity.User, error) - GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this - CreateUserFromExternal(user entity.User) error // Add this - UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this - GetAllUsers() ([]entity.User, error) // Add this + GetUserByNomorInduk(nomorInduk string) (entity.User, error) + CreateUserFromExternal(user entity.User) error + UpdateUserRole(nomorInduk string, roleID uuid.UUID) error + GetAllUsers() ([]entity.User, error) RegisterUser(user entity.User) error GetPendingUsers() ([]entity.User, error) UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error @@ -23,6 +23,7 @@ type UsersRepo interface { CreateSuperAdminBypass(user entity.User) error UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error UpdateUserRoleByUsername(username string, roleID uuid.UUID) error + GetUserByName(name string) (entity.User, error) } type usersRepo struct { @@ -35,6 +36,13 @@ func NewUsersRepo(db *gorm.DB) UsersRepo { } } +func (r *usersRepo) GetUserByName(name string) (entity.User, error) { + var user entity.User + // Use LOWER() function for case-insensitive comparison + err := r.db.Where("LOWER(name) = LOWER(?)", name).Preload("Role").First(&user).Error + return user, err +} + func (r *usersRepo) UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error { return r.db.Model(&entity.User{}).Where("id = ?", userID).Update("role_id", roleID).Error } diff --git a/usecase/users_usecase.go b/usecase/users_usecase.go index 641dbb9..aa58fca 100644 --- a/usecase/users_usecase.go +++ b/usecase/users_usecase.go @@ -7,6 +7,7 @@ import ( "users_management/m/model/entity" "users_management/m/repository" "users_management/m/utils" + "users_management/m/utils/validation" "github.com/go-playground/validator/v10" "github.com/google/uuid" @@ -15,10 +16,10 @@ import ( type UsersUsecase interface { GetRoleByDepartment(departmentName string) (uuid.UUID, error) GetUserByUsername(username string) (entity.User, error) - GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this - CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this - UpdateUserRole(nomorInduk, roleName string) error // Add this - GetAllUsers() ([]entity.User, error) // Add this + GetUserByNomorInduk(nomorInduk string) (entity.User, error) + CreateUserFromExternal(nomorInduk, name, roleName string) error + UpdateUserRole(nomorInduk, roleName string) error + GetAllUsers() ([]entity.User, error) RegisterUser(registerDTO dto.UserRegisterDTO) error GetPendingUsers() ([]dto.PendingUserResponse, error) ApproveUser(userID uuid.UUID) error @@ -28,6 +29,7 @@ type UsersUsecase interface { UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error UpdateUserRoleByUsername(username, newRoleName string) error GetUserByID(userID uuid.UUID) (entity.User, error) + GetUserByName(name string) (entity.User, error) } @@ -37,12 +39,19 @@ type usersUsecase struct { } func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase { + + validate := validator.New() + validation.RegisterCustomValidators(validate) return &usersUsecase{ userRepo: userRepo, - validate: validator.New(), + validate: validate, } } +func (u *usersUsecase) GetUserByName(name string) (entity.User, error) { + return u.userRepo.GetUserByName(name) +} + func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error { return u.userRepo.CreateSuperAdminBypass(user) @@ -92,7 +101,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error { if err := u.validate.Struct(registerDTO); err != nil { return err } - // Check if username already exists existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username) if err == nil && existingUser.ID != uuid.Nil { @@ -107,6 +115,11 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error { } } + existingUserByName, err := u.userRepo.GetUserByName(registerDTO.Name) + if err == nil && existingUserByName.ID != uuid.Nil { + return errors.New("name already exists, please use a different name") + } + // Hash password hashedPassword, err := utils.HashPassword(registerDTO.Password) if err != nil { @@ -119,7 +132,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error { } - // Create user with pending status user := entity.User{ ID: uuid.New(), diff --git a/utils/validation/validation_username.go b/utils/validation/validation_username.go new file mode 100644 index 0000000..8ebffe3 --- /dev/null +++ b/utils/validation/validation_username.go @@ -0,0 +1,48 @@ +package validation + +import ( + "regexp" + "strings" + "unicode" + + "github.com/go-playground/validator/v10" +) + +// RegisterCustomValidators registers custom validation rules +func RegisterCustomValidators(validate *validator.Validate) { + validate.RegisterValidation("nospace", validateNoSpace) + validate.RegisterValidation("alphanumunderscore", validateAlphaNumUnderscore) + validate.RegisterValidation("alphaspace", validateAlphaSpace) // Add this + validate.RegisterValidation("alphanum", validateAlphaNum) // Add this +} + +// validateNoSpace checks that the field contains no spaces +func validateNoSpace(fl validator.FieldLevel) bool { + value := fl.Field().String() + return !strings.Contains(value, " ") +} + +// validateAlphaNumUnderscore allows alphanumeric characters and underscores only +func validateAlphaNumUnderscore(fl validator.FieldLevel) bool { + value := fl.Field().String() + matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", value) + return matched +} + +// validateAlphaSpace allows alphabetic characters and spaces only (for names) +func validateAlphaSpace(fl validator.FieldLevel) bool { + value := fl.Field().String() + for _, char := range value { + if !unicode.IsLetter(char) && char != ' ' { + return false + } + } + return true +} + +// validateAlphaNum allows alphanumeric characters only +func validateAlphaNum(fl validator.FieldLevel) bool { + value := fl.Field().String() + matched, _ := regexp.MatchString("^[a-zA-Z0-9]+$", value) + return matched +} \ No newline at end of file