304 lines
7.9 KiB
Go
304 lines
7.9 KiB
Go
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
|
|
}
|