ce8a18aa8b
continuous-integration/drone/push Build is passing
- Introduced `form_test.go` to validate the behavior of the `UpdateUserForm` struct's JSON unmarshalling, specifically for the `profile_image` field. - Added tests to ensure omitted `profile_image` is not treated as an update, null values clear the image, and valid URLs are preserved. - Enhanced the `UpdateUserForm` and `UpdateProfileForm` structs to include logic for distinguishing between omitted and explicitly null profile images during JSON unmarshalling. This update improves the robustness of user profile updates by ensuring correct handling of profile image data in JSON requests, enhancing overall data integrity in the user management system.
208 lines
8.8 KiB
Go
208 lines
8.8 KiB
Go
package user
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"tm/pkg/response"
|
|
)
|
|
|
|
// User Registration DTOs
|
|
|
|
// CreateUserForm represents the data required to create a new user account
|
|
type CreateUserForm struct {
|
|
FullName string `json:"full_name" valid:"required,length(2|100)" example:"Admin User"` // Full name of the user
|
|
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"admin"` // Unique username (alphanumeric only)
|
|
Email string `json:"email" valid:"required,email" example:"admin@opplens.com"` // Valid email address
|
|
Password string `json:"password" valid:"required,length(8|128)" example:"Admin!1234"` // Password (minimum 8 characters)
|
|
Role string `json:"role" valid:"required,in(admin|manager|operator|viewer)" example:"admin"` // User role (admin, manager, operator, viewer)
|
|
Department *string `json:"department" valid:"optional,length(2|100)" example:"Information Technology"` // Optional department name
|
|
Position *string `json:"position" valid:"optional,length(2|100)" example:"Lead"` // Optional job position
|
|
Phone *string `json:"phone" valid:"optional,length(10|20)" example:"+18289784438"` // Optional phone number
|
|
ProfileImage *string `json:"profile_image" valid:"optional,url" example:""` // Optional profile image URL
|
|
}
|
|
|
|
// UpdateUserForm represents the data for updating an existing user account
|
|
type UpdateUserForm struct {
|
|
FullName *string `json:"full_name" valid:"optional,length(2|100)" example:"Admin User"` // Updated full name
|
|
Username *string `json:"username" valid:"optional,alphanum,length(3|30)" example:"admin"` // Updated username
|
|
Email *string `json:"email" valid:"optional,email" example:"admin@opplens.com"` // Updated email address
|
|
Role *string `json:"role" valid:"optional,in(admin|manager|operator|viewer)" example:"admin"` // Updated user role
|
|
Department *string `json:"department" valid:"optional,length(2|100)" example:"Information Technology"` // Updated department
|
|
Position *string `json:"position" valid:"optional,length(2|100)" example:"Lead"` // Updated position
|
|
Phone *string `json:"phone" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number
|
|
ProfileImage *string `json:"profile_image" valid:"optional,url" example:""` // Updated profile image URL
|
|
profileImageSet bool
|
|
}
|
|
|
|
func unmarshalProfileImageField(data []byte, profileImage **string, profileImageSet *bool) ([]byte, error) {
|
|
var raw map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profileImageRaw, ok := raw["profile_image"]
|
|
if ok {
|
|
*profileImageSet = true
|
|
if string(profileImageRaw) == "null" {
|
|
*profileImage = nil
|
|
} else {
|
|
var image string
|
|
if err := json.Unmarshal(profileImageRaw, &image); err != nil {
|
|
return nil, err
|
|
}
|
|
*profileImage = &image
|
|
}
|
|
delete(raw, "profile_image")
|
|
}
|
|
|
|
return json.Marshal(raw)
|
|
}
|
|
|
|
// UnmarshalJSON distinguishes omitted profile_image from explicit null to allow clearing the image.
|
|
func (f *UpdateUserForm) UnmarshalJSON(data []byte) error {
|
|
rest, err := unmarshalProfileImageField(data, &f.ProfileImage, &f.profileImageSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type alias UpdateUserForm
|
|
return json.Unmarshal(rest, (*alias)(f))
|
|
}
|
|
|
|
func (f *UpdateUserForm) isProfileImageSet() bool {
|
|
return f.profileImageSet
|
|
}
|
|
|
|
// User Authentication DTOs
|
|
|
|
// LoginForm represents the credentials required for user authentication
|
|
type LoginForm struct {
|
|
Username string `json:"username" valid:"required" example:"admin"` // Username or email address
|
|
Password string `json:"password" valid:"required" example:"Admin!1234"` // User password
|
|
DeviceToken string `json:"device_token" valid:"optional" example:"device_token"` // Device token
|
|
}
|
|
|
|
// RefreshTokenForm represents the refresh token required to generate new access tokens
|
|
type RefreshTokenForm struct {
|
|
RefreshToken string `json:"refresh_token" valid:"required" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."` // Valid refresh token
|
|
}
|
|
|
|
// ChangePasswordForm represents the data required to change user password
|
|
type ChangePasswordForm struct {
|
|
OldPassword string `json:"old_password" valid:"required" example:"Admin!1234"` // Current password for verification
|
|
NewPassword string `json:"new_password" valid:"required,length(8|128)" example:"NewAdmin!1234"` // New password (minimum 8 characters)
|
|
}
|
|
|
|
// UpdateProfileForm represents the data for updating user profile information
|
|
type UpdateProfileForm struct {
|
|
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"Admin User"` // Updated full name
|
|
Department *string `json:"department,omitempty" valid:"optional,length(2|100)" example:"Information Technology"` // Updated department
|
|
Position *string `json:"position,omitempty" valid:"optional,length(2|100)" example:"Lead"` // Updated position
|
|
Phone *string `json:"phone,omitempty" valid:"optional,length(10|20)" example:"+18289784438"` // Updated phone number
|
|
ProfileImage *string `json:"profile_image,omitempty" valid:"optional,url" example:""` // Updated profile image URL
|
|
profileImageSet bool
|
|
}
|
|
|
|
// UnmarshalJSON distinguishes omitted profile_image from explicit null to allow clearing the image.
|
|
func (f *UpdateProfileForm) UnmarshalJSON(data []byte) error {
|
|
rest, err := unmarshalProfileImageField(data, &f.ProfileImage, &f.profileImageSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type alias UpdateProfileForm
|
|
return json.Unmarshal(rest, (*alias)(f))
|
|
}
|
|
|
|
func (f *UpdateProfileForm) isProfileImageSet() bool {
|
|
return f.profileImageSet
|
|
}
|
|
|
|
// ResetPasswordForm represents the email address for password reset
|
|
type ResetPasswordForm struct {
|
|
Email string `json:"email" valid:"required,email" example:"admin@opplens.com"` // Email address for password reset
|
|
}
|
|
|
|
// Admin DTOs
|
|
type ListUsersForm struct {
|
|
Search *string `query:"q" valid:"optional"`
|
|
Status *string `query:"status" valid:"optional,in(active|inactive|suspended)"`
|
|
Role *string `query:"role" valid:"optional,in(admin|manager|operator|viewer)"`
|
|
CompanyID *string `query:"company_id" valid:"optional"`
|
|
Limit *int `query:"limit" valid:"optional,range(1|100)"`
|
|
Offset *int `query:"offset" valid:"optional,range(0|1000000)"`
|
|
SortBy *string `query:"sort_by" valid:"optional,in(full_name|email|username|role|created_at|last_login_at)"`
|
|
SortOrder *string `query:"sort_order" valid:"optional,in(asc|desc)"`
|
|
}
|
|
|
|
type UpdateUserStatusForm struct {
|
|
Status string `json:"status" valid:"required,in(active|inactive|suspended)"`
|
|
}
|
|
|
|
// AdminResetPasswordResponse is returned when an admin resets another user's password.
|
|
type AdminResetPasswordResponse struct {
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type UpdateUserRoleForm struct {
|
|
Role string `json:"role" valid:"required,in(admin|manager|operator|viewer)"`
|
|
}
|
|
|
|
// Response DTOs
|
|
type UserResponse struct {
|
|
ID string `json:"id"`
|
|
FullName string `json:"full_name"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Role string `json:"role"`
|
|
Status string `json:"status"`
|
|
Department *string `json:"department"`
|
|
Position *string `json:"position"`
|
|
Phone *string `json:"phone"`
|
|
ProfileImage *string `json:"profile_image"`
|
|
IsVerified bool `json:"is_verified"`
|
|
LastLoginAt *int64 `json:"last_login_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
DeviceToken []string `json:"-"`
|
|
}
|
|
|
|
type AuthResponse struct {
|
|
User *UserResponse `json:"user"`
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresAt int64 `json:"expires_at"`
|
|
}
|
|
|
|
type UserListResponse struct {
|
|
Users []*UserResponse `json:"users"`
|
|
Meta *response.Meta `json:"-"`
|
|
}
|
|
|
|
type SearchUsersForm struct {
|
|
Search *string `query:"search" valid:"optional"`
|
|
Status *string `query:"status" valid:"optional,in(active|inactive|suspended)"`
|
|
Role *string `query:"role" valid:"optional,in(admin|manager|operator|viewer)"`
|
|
}
|
|
|
|
// Helper function to convert User to UserResponse
|
|
func (u *User) ToResponse() *UserResponse {
|
|
return &UserResponse{
|
|
ID: u.ID.Hex(),
|
|
FullName: u.FullName,
|
|
Username: u.Username,
|
|
Email: u.Email,
|
|
Role: string(u.Role),
|
|
Status: string(u.Status),
|
|
Department: u.Department,
|
|
Position: u.Position,
|
|
Phone: u.Phone,
|
|
DeviceToken: u.DeviceToken,
|
|
ProfileImage: u.ProfileImage,
|
|
IsVerified: u.IsVerified,
|
|
LastLoginAt: u.LastLoginAt,
|
|
UpdatedAt: u.UpdatedAt,
|
|
CreatedAt: u.CreatedAt,
|
|
}
|
|
}
|