From 7f9874323aaffc0c904af5db92c8be48e884e141 Mon Sep 17 00:00:00 2001 From: qiaofeng1227 <76487013@qq.com> Date: Sat, 29 Nov 2025 15:40:54 +0800 Subject: [PATCH 1/3] docs: test issues update --- api-service/internal/model/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-service/internal/model/user.go b/api-service/internal/model/user.go index 3d0784a..b00af34 100644 --- a/api-service/internal/model/user.go +++ b/api-service/internal/model/user.go @@ -15,7 +15,7 @@ type User struct { Phone string `json:"phone" gorm:"size:20;comment:Phone number"` Gender int `json:"gender" gorm:"type:tinyint(1);default:0;comment:Gender: 0-unknown, 1-male, 2-female"` // 0:unknown, 1:male, 2:female Signature string `json:"signature" gorm:"size:255;comment:Personal signature"` - Status int `json:"status" gorm:"type:tinyint(1);default:1;index:idx_user_status;comment:Status: 0-disabled, 1-enabled"` // 1:active, 0:inactive + Status int `json:"status" gorm:"type:tinyint(1);default:1;index:idx_user_status;comment:Status: -1-deleted, 0-disabled, 1-enabled" binding:"min=-1,max=1"` // -1:deleted, 0:disabled, 1:enabled LastLoginAt *time.Time `json:"last_login_at" gorm:"type:datetime;serializer:datetime;comment:Last login time"` LastLoginIP string `json:"last_login_ip" gorm:"size:45;comment:Last login IP"` Timezone string `json:"timezone" gorm:"size:64;default:UTC;comment:Timezone"` From 426187f1f719de31698fcdc80840b8be1539422f Mon Sep 17 00:00:00 2001 From: qiaofeng1227 <76487013@qq.com> Date: Mon, 1 Dec 2025 11:47:43 +0800 Subject: [PATCH 2/3] fix: user status bug --- api-service/internal/dto/request/user.go | 4 ++-- api-service/internal/service/user.go | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/api-service/internal/dto/request/user.go b/api-service/internal/dto/request/user.go index 964f274..a68d725 100644 --- a/api-service/internal/dto/request/user.go +++ b/api-service/internal/dto/request/user.go @@ -41,7 +41,7 @@ type UserCreateRequest struct { 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) - Status *int `json:"status,omitempty" binding:"omitempty,min=0,max=1" example:"1"` // Status: 0-disabled, 1-enabled (optional) + Status *int `json:"status,omitempty" validate:"oneof=0 1" example:"1"` // Status: 0-disabled, 1-enabled (optional) Timezone *string `json:"timezone,omitempty" binding:"omitempty,max=64" example:"Asia/Shanghai"` // Timezone, default UTC (optional) Language *string `json:"language,omitempty" binding:"omitempty,max=10" example:"zh-CN"` // Language, default zh-CN (optional) RoleIDs []uint `json:"role_ids,omitempty" binding:"omitempty,dive,gt=0" example:"1,2"` // Array of role IDs to assign (optional) @@ -63,7 +63,7 @@ type UserUpdateRequest struct { // UserUpdateStatusRequest type UserUpdateStatusRequest struct { - Status int `json:"status" binding:"min=0,max=1" example:"1"` + Status int `json:"status" validate:"oneof=0 1" example:"1"` } // UserPasswordUpdateRequest diff --git a/api-service/internal/service/user.go b/api-service/internal/service/user.go index dcb4608..d78a18f 100644 --- a/api-service/internal/service/user.go +++ b/api-service/internal/service/user.go @@ -343,15 +343,20 @@ 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 + + // Phone can optionally start with + + startIndex := 0 + if strings.HasPrefix(phone, "+") { + startIndex = 1 } - // Check remaining characters are digits - for _, ch := range phone[1:] { + + // Check remaining characters are all digits + for _, ch := range phone[startIndex:] { if ch < '0' || ch > '9' { return false } From 18595bdb9c7761bf36e09cef5c0be534e42845d1 Mon Sep 17 00:00:00 2001 From: qiaofeng1227 <76487013@qq.com> Date: Mon, 1 Dec 2025 14:17:37 +0800 Subject: [PATCH 3/3] fix: update user status bug --- api-service/internal/interface/repository/user.go | 1 + api-service/internal/model/user.go | 2 +- api-service/internal/repository/user.go | 8 ++++++++ api-service/internal/service/user.go | 7 ++++--- api-service/internal/service/user_test.go | 8 ++++++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/api-service/internal/interface/repository/user.go b/api-service/internal/interface/repository/user.go index 050daec..b565137 100644 --- a/api-service/internal/interface/repository/user.go +++ b/api-service/internal/interface/repository/user.go @@ -16,6 +16,7 @@ type UserRepository interface { GetByEmail(ctx context.Context, email string) (*model.User, error) GetByUsernameOrEmail(ctx context.Context, usernameOrEmail string) (*model.User, error) Update(ctx context.Context, user *model.User) error + UpdateStatus(ctx context.Context, id uint, status int) error Delete(ctx context.Context, id uint) error // CreateUserRole creates a user-role association record. diff --git a/api-service/internal/model/user.go b/api-service/internal/model/user.go index b00af34..220244f 100644 --- a/api-service/internal/model/user.go +++ b/api-service/internal/model/user.go @@ -15,7 +15,7 @@ type User struct { Phone string `json:"phone" gorm:"size:20;comment:Phone number"` Gender int `json:"gender" gorm:"type:tinyint(1);default:0;comment:Gender: 0-unknown, 1-male, 2-female"` // 0:unknown, 1:male, 2:female Signature string `json:"signature" gorm:"size:255;comment:Personal signature"` - Status int `json:"status" gorm:"type:tinyint(1);default:1;index:idx_user_status;comment:Status: -1-deleted, 0-disabled, 1-enabled" binding:"min=-1,max=1"` // -1:deleted, 0:disabled, 1:enabled + Status int `json:"status" gorm:"type:tinyint(1);default:1;index:idx_user_status;comment:Status: -1-deleted, 0-disabled, 1-enabled"` // -1:deleted, 0:disabled, 1:enabled LastLoginAt *time.Time `json:"last_login_at" gorm:"type:datetime;serializer:datetime;comment:Last login time"` LastLoginIP string `json:"last_login_ip" gorm:"size:45;comment:Last login IP"` Timezone string `json:"timezone" gorm:"size:64;default:UTC;comment:Timezone"` diff --git a/api-service/internal/repository/user.go b/api-service/internal/repository/user.go index f277e80..68333ab 100644 --- a/api-service/internal/repository/user.go +++ b/api-service/internal/repository/user.go @@ -111,6 +111,14 @@ func (r *userRepository) Update(ctx context.Context, user *model.User) error { return nil } +// UpdateStatus updates a user's status explicitly, including zero values +func (r *userRepository) UpdateStatus(ctx context.Context, id uint, status int) error { + if err := r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Update("status", status).Error; err != nil { + return errors.NewAppErrorWrapError(err, errors.CodeRecordUpdateFailed) + } + return nil +} + // Delete deletes a user (soft delete) func (r *userRepository) Delete(ctx context.Context, id uint) error { // soft delete: set status = -1 and update updated_at diff --git a/api-service/internal/service/user.go b/api-service/internal/service/user.go index d78a18f..2404b6b 100644 --- a/api-service/internal/service/user.go +++ b/api-service/internal/service/user.go @@ -247,13 +247,14 @@ func (s *userService) syncUserRoles(ctx context.Context, currentUserID, userID u func (s *userService) UpdateUserStatus(ctx context.Context, userID uint, req *request.UserUpdateStatusRequest) error { s.logger.InfoContext(ctx, "Updating user status", logger.Uint("user_id", userID), logger.Int("status", req.Status)) - user, err := s.userRepo.GetByID(ctx, userID) + // Check if user exists + _, err := s.userRepo.GetByID(ctx, userID) if err != nil { return err } - user.Status = req.Status - if err := s.userRepo.Update(ctx, user); err != nil { + // Use UpdateStatus to explicitly update status field, including zero values + if err := s.userRepo.UpdateStatus(ctx, userID, req.Status); err != nil { s.logger.ErrorContext(ctx, "Failed to update user status", logger.ErrorField(err)) return err } diff --git a/api-service/internal/service/user_test.go b/api-service/internal/service/user_test.go index d0c2a0c..1b762c5 100644 --- a/api-service/internal/service/user_test.go +++ b/api-service/internal/service/user_test.go @@ -73,11 +73,15 @@ func (m *MockUserRepository) Update(ctx context.Context, user *model.User) error return args.Error(0) } +func (m *MockUserRepository) UpdateStatus(ctx context.Context, id uint, status int) error { + args := m.Called(ctx, id, status) + return args.Error(0) +} + func (m *MockUserRepository) Delete(ctx context.Context, id uint) error { args := m.Called(ctx, id) return args.Error(0) } - func (m *MockUserRepository) List(ctx context.Context, offset, limit int, filters map[string]interface{}) ([]*model.User, int64, error) { args := m.Called(ctx, offset, limit, filters) return args.Get(0).([]*model.User), args.Get(1).(int64), args.Error(2) @@ -536,7 +540,7 @@ func TestUserService_UpdateUserStatus_Success(t *testing.T) { } mockRepo.On("GetByID", ctx, userID).Return(testUser, nil) - mockRepo.On("Update", ctx, mock.AnythingOfType("*model.User")).Return(nil) + mockRepo.On("UpdateStatus", ctx, userID, UserStatusInactive).Return(nil) err := service.UpdateUserStatus(ctx, userID, req)