implement graphQL and init postgres
This commit is contained in:
@@ -13,42 +13,98 @@ import (
|
||||
// User represents a user in the system
|
||||
type User struct {
|
||||
BaseModel
|
||||
Email string `json:"email" gorm:"unique;not null;type:varchar(255)"`
|
||||
Username string `json:"username" gorm:"unique;not null;type:varchar(100)"`
|
||||
Name string `json:"name" gorm:"not null;type:varchar(255)"`
|
||||
PasswordHash string `json:"-" gorm:"not null;type:text"`
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
EmailVerified bool `json:"emailVerified" gorm:"default:false"`
|
||||
EmailVerificationToken string `json:"-" gorm:"type:varchar(255)"`
|
||||
PasswordResetToken string `json:"-" gorm:"type:varchar(255)"`
|
||||
PasswordResetExpires *time.Time `json:"-"`
|
||||
LastLogin *time.Time `json:"lastLogin"`
|
||||
LoginAttempts int `json:"-" gorm:"default:0"`
|
||||
LockedUntil *time.Time `json:"-"`
|
||||
TwoFactorEnabled bool `json:"twoFactorEnabled" gorm:"default:false"`
|
||||
TwoFactorSecret string `json:"-" gorm:"type:varchar(255)"`
|
||||
Roles []Role `json:"roles" gorm:"many2many:user_roles;"`
|
||||
AuditLogs []AuditLog `json:"-" gorm:"foreignKey:UserID"`
|
||||
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"`
|
||||
Username string `json:"username" validate:"required,min=3,max=50"`
|
||||
Name string `json:"name" validate:"required,min=2,max=100"`
|
||||
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"`
|
||||
Username *string `json:"username,omitempty" validate:"omitempty,min=3,max=50"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=2,max=100"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
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"`
|
||||
@@ -65,16 +121,13 @@ type UserLoginResponse struct {
|
||||
|
||||
// UserInfo represents public user information
|
||||
type UserInfo struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
LastLogin *time.Time `json:"lastLogin"`
|
||||
Roles []RoleInfo `json:"roles"`
|
||||
Permissions []string `json:"permissions"`
|
||||
DateCreated time.Time `json:"dateCreated"`
|
||||
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
|
||||
@@ -98,10 +151,9 @@ type ResetPasswordConfirmRequest struct {
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
u.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize email and username
|
||||
// Normalize email and full name
|
||||
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
|
||||
u.Username = strings.ToLower(strings.TrimSpace(u.Username))
|
||||
u.Name = strings.TrimSpace(u.Name)
|
||||
u.FullName = strings.TrimSpace(u.FullName)
|
||||
|
||||
return u.Validate()
|
||||
}
|
||||
@@ -114,11 +166,8 @@ func (u *User) BeforeUpdate(tx *gorm.DB) error {
|
||||
if u.Email != "" {
|
||||
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
|
||||
}
|
||||
if u.Username != "" {
|
||||
u.Username = strings.ToLower(strings.TrimSpace(u.Username))
|
||||
}
|
||||
if u.Name != "" {
|
||||
u.Name = strings.TrimSpace(u.Name)
|
||||
if u.FullName != "" {
|
||||
u.FullName = strings.TrimSpace(u.FullName)
|
||||
}
|
||||
|
||||
return u.Validate()
|
||||
@@ -134,24 +183,8 @@ func (u *User) Validate() error {
|
||||
return errors.New("invalid email format")
|
||||
}
|
||||
|
||||
if u.Username == "" {
|
||||
return errors.New("username is required")
|
||||
}
|
||||
|
||||
if len(u.Username) < 3 || len(u.Username) > 50 {
|
||||
return errors.New("username must be between 3 and 50 characters")
|
||||
}
|
||||
|
||||
if !isValidUsername(u.Username) {
|
||||
return errors.New("username can only contain letters, numbers, underscores, and hyphens")
|
||||
}
|
||||
|
||||
if u.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
if len(u.Name) < 2 || len(u.Name) > 100 {
|
||||
return errors.New("name must be between 2 and 100 characters")
|
||||
if u.FullName != "" && len(u.FullName) > 255 {
|
||||
return errors.New("full name must not exceed 255 characters")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -182,55 +215,16 @@ func (u *User) VerifyPassword(plainPassword string) bool {
|
||||
return u.CheckPassword(plainPassword)
|
||||
}
|
||||
|
||||
// IsLocked checks if the user account is locked
|
||||
func (u *User) IsLocked() bool {
|
||||
if u.LockedUntil == nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().Before(*u.LockedUntil)
|
||||
}
|
||||
|
||||
// Lock locks the user account for the specified duration
|
||||
func (u *User) Lock(duration time.Duration) {
|
||||
lockUntil := time.Now().Add(duration)
|
||||
u.LockedUntil = &lockUntil
|
||||
}
|
||||
|
||||
// Unlock unlocks the user account
|
||||
func (u *User) Unlock() {
|
||||
u.LockedUntil = nil
|
||||
u.LoginAttempts = 0
|
||||
}
|
||||
|
||||
// IncrementLoginAttempts increments the login attempt counter
|
||||
func (u *User) IncrementLoginAttempts() {
|
||||
u.LoginAttempts++
|
||||
}
|
||||
|
||||
// ResetLoginAttempts resets the login attempt counter
|
||||
func (u *User) ResetLoginAttempts() {
|
||||
u.LoginAttempts = 0
|
||||
}
|
||||
|
||||
// UpdateLastLogin updates the last login timestamp
|
||||
func (u *User) UpdateLastLogin() {
|
||||
now := time.Now()
|
||||
u.LastLogin = &now
|
||||
}
|
||||
|
||||
// ToUserInfo converts User to UserInfo (public information)
|
||||
func (u *User) ToUserInfo() UserInfo {
|
||||
userInfo := UserInfo{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
Username: u.Username,
|
||||
Name: u.Name,
|
||||
Active: u.Active,
|
||||
EmailVerified: u.EmailVerified,
|
||||
LastLogin: u.LastLogin,
|
||||
DateCreated: u.DateCreated,
|
||||
Roles: make([]RoleInfo, len(u.Roles)),
|
||||
Permissions: []string{},
|
||||
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
|
||||
@@ -250,6 +244,23 @@ func (u *User) ToUserInfo() UserInfo {
|
||||
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 {
|
||||
@@ -278,12 +289,6 @@ func isValidEmail(email string) bool {
|
||||
return emailRegex.MatchString(email)
|
||||
}
|
||||
|
||||
// isValidUsername validates username format
|
||||
func isValidUsername(username string) bool {
|
||||
usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
return usernameRegex.MatchString(username)
|
||||
}
|
||||
|
||||
// validatePassword validates password strength
|
||||
func validatePassword(password string) error {
|
||||
if len(password) < 8 {
|
||||
@@ -294,25 +299,5 @@ func validatePassword(password string) error {
|
||||
return errors.New("password must not exceed 128 characters")
|
||||
}
|
||||
|
||||
// Check for at least one lowercase letter
|
||||
if matched, _ := regexp.MatchString(`[a-z]`, password); !matched {
|
||||
return errors.New("password must contain at least one lowercase letter")
|
||||
}
|
||||
|
||||
// Check for at least one uppercase letter
|
||||
if matched, _ := regexp.MatchString(`[A-Z]`, password); !matched {
|
||||
return errors.New("password must contain at least one uppercase letter")
|
||||
}
|
||||
|
||||
// Check for at least one digit
|
||||
if matched, _ := regexp.MatchString(`\d`, password); !matched {
|
||||
return errors.New("password must contain at least one digit")
|
||||
}
|
||||
|
||||
// Check for at least one special character
|
||||
if matched, _ := regexp.MatchString(`[!@#$%^&*(),.?":{}|<>]`, password); !matched {
|
||||
return errors.New("password must contain at least one special character")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user