package model import ( "errors" "omega-server/local/utl/password" "regexp" "strings" "time" "gorm.io/gorm" ) // User represents a user in the system type User struct { BaseModel Email string `json:"email" gorm:"unique;not null;type:varchar(255)"` PasswordHash string `json:"-" gorm:"not null;type:varchar(255)"` FullName string `json:"full_name" gorm:"type:varchar(255)"` Roles []Role `json:"roles" gorm:"many2many:user_roles;"` } // UserCreateRequest represents the request to create a new user type UserCreateRequest struct { Email string `json:"email" validate:"required,email"` FullName string `json:"full_name" validate:"required,min=2,max=100"` Password string `json:"password" validate:"required,min=8"` RoleIDs []string `json:"roleIds"` } // ToUser converts UserCreateRequest to User domain model func (req *UserCreateRequest) ToUser() (*User, error) { user := &User{ Email: req.Email, FullName: req.FullName, } // Handle password hashing if err := user.SetPassword(req.Password); err != nil { return nil, err } // Note: Roles will be set by the service layer after validation return user, nil } // Validate validates the UserCreateRequest func (req *UserCreateRequest) Validate() error { if req.Email == "" { return errors.New("email is required") } if !isValidEmail(req.Email) { return errors.New("invalid email format") } if len(req.Password) < 8 { return errors.New("password must be at least 8 characters") } if req.FullName == "" { return errors.New("full name is required") } return nil } // UserUpdateRequest represents the request to update a user type UserUpdateRequest struct { Email *string `json:"email,omitempty" validate:"omitempty,email"` FullName *string `json:"full_name,omitempty" validate:"omitempty,min=2,max=100"` RoleIDs []string `json:"roleIds,omitempty"` } // ApplyToUser applies the UserUpdateRequest to an existing User func (req *UserUpdateRequest) ApplyToUser(user *User) error { if req.Email != nil { user.Email = *req.Email } if req.FullName != nil { user.FullName = *req.FullName } // Note: Roles will be handled by the service layer return nil } // Validate validates the UserUpdateRequest func (req *UserUpdateRequest) Validate() error { if req.Email != nil && !isValidEmail(*req.Email) { return errors.New("invalid email format") } if req.FullName != nil && len(*req.FullName) == 0 { return errors.New("full name cannot be empty") } if req.FullName != nil && len(*req.FullName) > 255 { return errors.New("full name must not exceed 255 characters") } return nil } // UserResponse represents the response when returning user data type UserResponse struct { ID string `json:"id"` Email string `json:"email"` FullName string `json:"fullName"` Roles []string `json:"roles"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } // UserLoginRequest represents a login request type UserLoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } // UserLoginResponse represents a login response type UserLoginResponse struct { Token string `json:"token"` RefreshToken string `json:"refreshToken"` ExpiresAt time.Time `json:"expiresAt"` User UserInfo `json:"user"` } // UserInfo represents public user information type UserInfo struct { ID string `json:"id"` Email string `json:"email"` FullName string `json:"full_name"` Roles []RoleInfo `json:"roles"` Permissions []string `json:"permissions"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // ChangePasswordRequest represents a password change request type ChangePasswordRequest struct { CurrentPassword string `json:"currentPassword" validate:"required"` NewPassword string `json:"newPassword" validate:"required,min=8"` } // ResetPasswordRequest represents a password reset request type ResetPasswordRequest struct { Email string `json:"email" validate:"required,email"` } // ResetPasswordConfirmRequest represents a password reset confirmation type ResetPasswordConfirmRequest struct { Token string `json:"token" validate:"required"` NewPassword string `json:"newPassword" validate:"required,min=8"` } // BeforeCreate is called before creating a user func (u *User) BeforeCreate(tx *gorm.DB) error { u.BaseModel.BeforeCreate() // Normalize email and full name u.Email = strings.ToLower(strings.TrimSpace(u.Email)) u.FullName = strings.TrimSpace(u.FullName) return u.Validate() } // BeforeUpdate is called before updating a user func (u *User) BeforeUpdate(tx *gorm.DB) error { u.BaseModel.BeforeUpdate() // Normalize fields if they're being updated if u.Email != "" { u.Email = strings.ToLower(strings.TrimSpace(u.Email)) } if u.FullName != "" { u.FullName = strings.TrimSpace(u.FullName) } return u.Validate() } // Validate validates user data func (u *User) Validate() error { if u.Email == "" { return errors.New("email is required") } if !isValidEmail(u.Email) { return errors.New("invalid email format") } if u.FullName != "" && len(u.FullName) > 255 { return errors.New("full name must not exceed 255 characters") } return nil } // SetPassword sets the user's password hash func (u *User) SetPassword(plainPassword string) error { if err := validatePassword(plainPassword); err != nil { return err } hash, err := password.HashPassword(plainPassword) if err != nil { return err } u.PasswordHash = hash return nil } // CheckPassword verifies the user's password func (u *User) CheckPassword(plainPassword string) bool { return password.CheckPasswordHash(plainPassword, u.PasswordHash) } // VerifyPassword verifies the user's password (alias for CheckPassword) func (u *User) VerifyPassword(plainPassword string) bool { return u.CheckPassword(plainPassword) } // ToUserInfo converts User to UserInfo (public information) func (u *User) ToUserInfo() UserInfo { userInfo := UserInfo{ ID: u.ID, Email: u.Email, FullName: u.FullName, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, Roles: make([]RoleInfo, len(u.Roles)), Permissions: []string{}, } // Convert roles and collect permissions permissionSet := make(map[string]bool) for i, role := range u.Roles { userInfo.Roles[i] = role.ToRoleInfo() for _, permission := range role.Permissions { permissionSet[permission.Name] = true } } // Convert permission set to slice for permission := range permissionSet { userInfo.Permissions = append(userInfo.Permissions, permission) } return userInfo } // ToResponse converts User to UserResponse (for API responses) func (u *User) ToResponse() *UserResponse { roleNames := make([]string, len(u.Roles)) for i, role := range u.Roles { roleNames[i] = role.Name } return &UserResponse{ ID: u.ID, Email: u.Email, FullName: u.FullName, Roles: roleNames, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, } } // HasRole checks if the user has a specific role func (u *User) HasRole(roleName string) bool { for _, role := range u.Roles { if role.Name == roleName { return true } } return false } // HasPermission checks if the user has a specific permission func (u *User) HasPermission(permissionName string) bool { for _, role := range u.Roles { for _, permission := range role.Permissions { if permission.Name == permissionName { return true } } } return false } // isValidEmail validates email format func isValidEmail(email string) bool { emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) return emailRegex.MatchString(email) } // validatePassword validates password strength func validatePassword(password string) error { if len(password) < 8 { return errors.New("password must be at least 8 characters long") } if len(password) > 128 { return errors.New("password must not exceed 128 characters") } return nil }