fixing validation for username and full name

This commit is contained in:
areeqakbr 2025-06-22 17:19:18 +07:00
parent 433512e3da
commit f48d61d4e2
5 changed files with 86 additions and 18 deletions

View File

@ -16,10 +16,10 @@ const (
) )
type UserRegisterDTO struct { type UserRegisterDTO struct {
Name string `json:"name" validate:"required"` Name string `json:"name" validate:"required,min=2,max=100,alphaspace"`
Username string `json:"username" validate:"required"` Username string `json:"username" validate:"required,min=3,max=50,alphanumunderscore,nospace"`
Password string `json:"password" validate:"required,min=6"` Password string `json:"password" validate:"required,min=6,max=100"`
NomorInduk string `json:"nomor_induk" validate:"required"` NomorInduk string `json:"nomor_induk" validate:"required,min=3,max=50,alphanum"`
} }
type UserLoginDTO struct { type UserLoginDTO struct {

View File

@ -16,13 +16,13 @@ const (
type User struct { type User struct {
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` 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"` RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"`
Role Role `json:"role" gorm:"foreignKey:RoleID"` Role Role `json:"role" gorm:"foreignKey:RoleID"`
Name string `json:"name"` Name string `json:"name" gorm:"unique"`
Username string `json:"username" gorm:"unique"` Username string `json:"username" gorm:"unique"`
Password string `json:"password"` 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"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }

View File

@ -11,10 +11,10 @@ type UsersRepo interface {
Post(user entity.User) error Post(user entity.User) error
GetRoleByDepartment(departmentName string) (entity.Role, error) GetRoleByDepartment(departmentName string) (entity.Role, error)
GetUserByUsername(username string) (entity.User, error) GetUserByUsername(username string) (entity.User, error)
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this GetUserByNomorInduk(nomorInduk string) (entity.User, error)
CreateUserFromExternal(user entity.User) error // Add this CreateUserFromExternal(user entity.User) error
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this UpdateUserRole(nomorInduk string, roleID uuid.UUID) error
GetAllUsers() ([]entity.User, error) // Add this GetAllUsers() ([]entity.User, error)
RegisterUser(user entity.User) error RegisterUser(user entity.User) error
GetPendingUsers() ([]entity.User, error) GetPendingUsers() ([]entity.User, error)
UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error
@ -23,6 +23,7 @@ type UsersRepo interface {
CreateSuperAdminBypass(user entity.User) error CreateSuperAdminBypass(user entity.User) error
UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error
UpdateUserRoleByUsername(username string, roleID uuid.UUID) error UpdateUserRoleByUsername(username string, roleID uuid.UUID) error
GetUserByName(name string) (entity.User, error)
} }
type usersRepo struct { 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 { 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 return r.db.Model(&entity.User{}).Where("id = ?", userID).Update("role_id", roleID).Error
} }

View File

@ -7,6 +7,7 @@ import (
"users_management/m/model/entity" "users_management/m/model/entity"
"users_management/m/repository" "users_management/m/repository"
"users_management/m/utils" "users_management/m/utils"
"users_management/m/utils/validation"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/google/uuid" "github.com/google/uuid"
@ -15,10 +16,10 @@ import (
type UsersUsecase interface { type UsersUsecase interface {
GetRoleByDepartment(departmentName string) (uuid.UUID, error) GetRoleByDepartment(departmentName string) (uuid.UUID, error)
GetUserByUsername(username string) (entity.User, error) GetUserByUsername(username string) (entity.User, error)
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this GetUserByNomorInduk(nomorInduk string) (entity.User, error)
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this CreateUserFromExternal(nomorInduk, name, roleName string) error
UpdateUserRole(nomorInduk, roleName string) error // Add this UpdateUserRole(nomorInduk, roleName string) error
GetAllUsers() ([]entity.User, error) // Add this GetAllUsers() ([]entity.User, error)
RegisterUser(registerDTO dto.UserRegisterDTO) error RegisterUser(registerDTO dto.UserRegisterDTO) error
GetPendingUsers() ([]dto.PendingUserResponse, error) GetPendingUsers() ([]dto.PendingUserResponse, error)
ApproveUser(userID uuid.UUID) error ApproveUser(userID uuid.UUID) error
@ -28,6 +29,7 @@ type UsersUsecase interface {
UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error
UpdateUserRoleByUsername(username, newRoleName string) error UpdateUserRoleByUsername(username, newRoleName string) error
GetUserByID(userID uuid.UUID) (entity.User, 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 { func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
validate := validator.New()
validation.RegisterCustomValidators(validate)
return &usersUsecase{ return &usersUsecase{
userRepo: userRepo, 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 { func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error {
return u.userRepo.CreateSuperAdminBypass(user) 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 { if err := u.validate.Struct(registerDTO); err != nil {
return err return err
} }
// Check if username already exists // Check if username already exists
existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username) existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username)
if err == nil && existingUser.ID != uuid.Nil { 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 // Hash password
hashedPassword, err := utils.HashPassword(registerDTO.Password) hashedPassword, err := utils.HashPassword(registerDTO.Password)
if err != nil { if err != nil {
@ -119,7 +132,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
} }
// Create user with pending status // Create user with pending status
user := entity.User{ user := entity.User{
ID: uuid.New(), ID: uuid.New(),

View File

@ -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
}