Files
omega-server/local/model/user.go
2025-07-06 19:19:36 +02:00

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
}