diff --git a/api-service/internal/dto/request/user.go b/api-service/internal/dto/request/user.go index e24fe58..964f274 100644 --- a/api-service/internal/dto/request/user.go +++ b/api-service/internal/dto/request/user.go @@ -33,11 +33,11 @@ type UserListRequest struct { // UserCreateRequest defines the request structure for creating a user. // Includes user basic information and supports assigning multiple roles via role_ids. type UserCreateRequest struct { - Username string `json:"username" binding:"required,max=64" example:"johndoe"` // Username, 3-32 characters, letters, numbers, underscore + Username string `json:"username" binding:"required,min=3,max=32" example:"johndoe"` // Username, 3-32 characters, letters, numbers, underscore Email string `json:"email" binding:"required,email" example:"john@example.com"` // Email address, must be unique - Password string `json:"password" binding:"required" example:"password123"` // Password, 8-32 characters, must contain uppercase + Password string `json:"password" binding:"required,min=8,max=32" example:"Password123"` // Password, 8-32 characters, must contain uppercase Nickname *string `json:"nickname,omitempty" binding:"omitempty,max=64" example:"John Doe"` // User nickname (optional) - Phone *string `json:"phone,omitempty" binding:"omitempty,max=20" example:"+1234567890"` // Phone number (optional) + Phone *string `json:"phone,omitempty" binding:"omitempty,min=8,max=20" example:"+1234567890"` // Phone number (optional) Avatar *string `json:"avatar,omitempty" binding:"omitempty,url" example:"https://example.com/avatar.jpg"` // Avatar URL (optional) Gender *int `json:"gender,omitempty" binding:"omitempty,min=0,max=2" example:"1"` // Gender: 1-male, 2-female, 0-unknown (optional) Signature *string `json:"signature,omitempty" binding:"omitempty,max=255" example:"This is my signature"` // Personal signature (optional) @@ -49,10 +49,10 @@ type UserCreateRequest struct { // UserUpdateRequest type UserUpdateRequest struct { - Username *string `json:"username,omitempty" binding:"omitempty,max=64" example:"johndoe"` + Username *string `json:"username,omitempty" binding:"omitempty,min=3,max=32" example:"johndoe"` Email *string `json:"email,omitempty" binding:"omitempty,email" example:"newemail@example.com"` Nickname *string `json:"nickname,omitempty" binding:"omitempty,max=64" example:"John Doe"` - Phone *string `json:"phone,omitempty" binding:"omitempty,max=20" example:"+1234567890"` + Phone *string `json:"phone,omitempty" binding:"omitempty,min=8,max=20" example:"+1234567890"` Avatar *string `json:"avatar,omitempty" binding:"omitempty,url" example:"https://example.com/avatar.jpg"` Gender *int `json:"gender,omitempty" binding:"omitempty,min=0,max=2" example:"1"` Signature *string `json:"signature,omitempty" binding:"omitempty,max=255" example:"This is my signature"` @@ -68,7 +68,7 @@ type UserUpdateStatusRequest struct { // UserPasswordUpdateRequest type UserPasswordUpdateRequest struct { - NewPassword string `json:"new_password" binding:"required,min=6" example:"newpass123"` + NewPassword string `json:"new_password" binding:"required,min=8,max=32" example:"newpass123"` ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=NewPassword" example:"newpass123"` } diff --git a/api-service/internal/service/user.go b/api-service/internal/service/user.go index af9c3fc..dcb4608 100644 --- a/api-service/internal/service/user.go +++ b/api-service/internal/service/user.go @@ -11,6 +11,7 @@ import ( "api-service/pkg/errors" "api-service/pkg/logger" "context" + "strings" "time" ) @@ -163,6 +164,11 @@ func (s *userService) UpdateUser(ctx context.Context, currentUserID, userID uint } } + // 2.5. If updating phone, check format + if req.Phone != nil && !validatePhoneFormat(*req.Phone) { + return nil, errors.NewAppError(errors.CodeInvalidPhoneFormat) + } + // 3. Update user info s.updateUserFromRequest(user, req) @@ -335,6 +341,23 @@ func (s *userService) validateEmailUniqueness(ctx context.Context, email string, } // validateUserCreation validates user creation + +// validatePhoneFormat validates phone number format (must start with + and contain only digits) +func validatePhoneFormat(phone string) bool { + if phone == "" { + return true // Phone is optional + } + if !strings.HasPrefix(phone, "+") { + return false + } + // Check remaining characters are digits + for _, ch := range phone[1:] { + if ch < '0' || ch > '9' { + return false + } + } + return true +} func (s *userService) validateUserCreation(ctx context.Context, req *request.UserCreateRequest) error { // Check if username exists exists, err := s.userRepo.ExistsByUsername(ctx, req.Username) @@ -346,6 +369,11 @@ func (s *userService) validateUserCreation(ctx context.Context, req *request.Use return errors.ErrUserAlreadyExists } + // Check phone format (only if provided) + if req.Phone != nil && !validatePhoneFormat(*req.Phone) { + return errors.NewAppError(errors.CodeInvalidPhoneFormat) + } + // Check if email exists exists, err = s.userRepo.ExistsByEmail(ctx, req.Email) if err != nil {