Refactor User Management API and Update Documentation
- Changed API tags from "Admin-Users" to "Admin-Authorization" for better clarity in user management endpoints. - Removed unused endpoints for retrieving users by company ID and role, streamlining the user management functionality. - Updated user entity and forms to reflect new example values for improved clarity in API documentation. - Enhanced pagination handling in user listing responses, ensuring consistent metadata structure. - Updated API documentation to reflect changes in endpoint structure and response formats, improving clarity for API consumers.
This commit is contained in:
+177
-280
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
"tm/pkg/authorization"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -14,21 +15,20 @@ import (
|
||||
|
||||
// Service defines business logic for user operations
|
||||
type Service interface {
|
||||
CreateUser(ctx context.Context, form *CreateUserForm, createdBy *string) (*User, error)
|
||||
// Main CRUD
|
||||
Register(ctx context.Context, form *CreateUserForm) (*User, error)
|
||||
Update(ctx context.Context, id string, form *UpdateUserForm) (*User, error)
|
||||
UpdateStatus(ctx context.Context, id string, form *UpdateUserStatusForm) error
|
||||
GetUserByID(ctx context.Context, userID string) (*User, error)
|
||||
Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*UserListResponse, error)
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// Profile
|
||||
Login(ctx context.Context, form *LoginForm) (*AuthResponse, error)
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
|
||||
GetProfile(ctx context.Context, userID string) (*User, error)
|
||||
UpdateProfile(ctx context.Context, userID string, form *UpdateProfileForm) error
|
||||
RefreshToken(ctx context.Context, refreshToken string) (*AuthResponse, error)
|
||||
ChangePassword(ctx context.Context, userID string, form *ChangePasswordForm) error
|
||||
ResetPassword(ctx context.Context, form *ResetPasswordForm) error
|
||||
GetUserByID(ctx context.Context, id string) (*User, error)
|
||||
UpdateUser(ctx context.Context, id string, form *UpdateUserForm, updatedBy *string) (*User, error)
|
||||
DeleteUser(ctx context.Context, id string, deletedBy *string) error
|
||||
ListUsers(ctx context.Context, form *ListUsersForm) (*UserListResponse, error)
|
||||
UpdateUserStatus(ctx context.Context, id string, form *UpdateUserStatusForm, updatedBy *string) error
|
||||
UpdateUserRole(ctx context.Context, id string, form *UpdateUserRoleForm, updatedBy *string) error
|
||||
GetUsersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*User, int64, error)
|
||||
GetUsersByRole(ctx context.Context, role UserRole, limit, offset int) ([]*User, error)
|
||||
Logout(ctx context.Context, userID string, accessToken string) error
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ func NewUserService(repository Repository, logger logger.Logger, authService aut
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser creates a new user
|
||||
func (s *userService) CreateUser(ctx context.Context, form *CreateUserForm, createdBy *string) (*User, error) {
|
||||
// Register creates a new user
|
||||
func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User, error) {
|
||||
// Check if email already exists
|
||||
existingUser, _ := s.repository.GetByEmail(ctx, form.Email)
|
||||
if existingUser != nil {
|
||||
@@ -91,14 +91,13 @@ func (s *userService) CreateUser(ctx context.Context, form *CreateUserForm, crea
|
||||
Position: form.Position,
|
||||
Phone: form.Phone,
|
||||
ProfileImage: form.ProfileImage,
|
||||
IsVerified: false,
|
||||
CreatedBy: createdBy,
|
||||
IsVerified: true,
|
||||
}
|
||||
|
||||
// Save to database
|
||||
err = s.repository.Create(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create user", map[string]interface{}{
|
||||
s.logger.Error("Failed to register user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": form.Email,
|
||||
"username": form.Username,
|
||||
@@ -106,17 +105,169 @@ func (s *userService) CreateUser(ctx context.Context, form *CreateUserForm, crea
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("User created successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
"username": user.Username,
|
||||
"role": user.Role,
|
||||
"created_by": createdBy,
|
||||
s.logger.Info("User registered successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
"username": user.Username,
|
||||
"role": user.Role,
|
||||
})
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Update updates user information
|
||||
func (s *userService) Update(ctx context.Context, id string, form *UpdateUserForm) (*User, error) {
|
||||
// Get user
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if form.FullName != nil {
|
||||
user.FullName = *form.FullName
|
||||
}
|
||||
|
||||
if form.Username != nil {
|
||||
// Check if username already exists
|
||||
existingUser, _ := s.repository.GetByUsername(ctx, *form.Username)
|
||||
if existingUser != nil && existingUser.ID.Hex() != id {
|
||||
return nil, errors.New("username already exists")
|
||||
}
|
||||
user.Username = *form.Username
|
||||
}
|
||||
|
||||
if form.Email != nil {
|
||||
// Check if email already exists
|
||||
existingUser, _ := s.repository.GetByEmail(ctx, *form.Email)
|
||||
if existingUser != nil && existingUser.ID.Hex() != id {
|
||||
return nil, errors.New("email already exists")
|
||||
}
|
||||
user.Email = *form.Email
|
||||
}
|
||||
|
||||
if form.Role != nil {
|
||||
role := UserRole(*form.Role)
|
||||
if !s.validator.IsValidRole(role) {
|
||||
return nil, errors.New("invalid role")
|
||||
}
|
||||
user.Role = role
|
||||
}
|
||||
|
||||
if form.Department != nil {
|
||||
user.Department = form.Department
|
||||
}
|
||||
|
||||
if form.Position != nil {
|
||||
user.Position = form.Position
|
||||
}
|
||||
|
||||
if form.Phone != nil {
|
||||
user.Phone = form.Phone
|
||||
}
|
||||
|
||||
if form.ProfileImage != nil {
|
||||
user.ProfileImage = form.ProfileImage
|
||||
}
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update user")
|
||||
}
|
||||
|
||||
s.logger.Info("User updated successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
})
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateStatus updates user status
|
||||
func (s *userService) UpdateStatus(ctx context.Context, id string, form *UpdateUserStatusForm) error {
|
||||
// Get user
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// Update status
|
||||
user.Status = UserStatus(form.Status)
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update user status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
"status": form.Status,
|
||||
})
|
||||
return errors.New("failed to update user status")
|
||||
}
|
||||
|
||||
s.logger.Info("User status updated successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
"status": form.Status,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByID retrieves a user by ID
|
||||
func (s *userService) GetUserByID(ctx context.Context, id string) (*User, error) {
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Search searches users with search and filters
|
||||
func (s *userService) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*UserListResponse, error) {
|
||||
// Get users
|
||||
users, total, err := s.repository.Search(ctx, form, pagination)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list users", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to list users")
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
var userResponses []*UserResponse
|
||||
for _, user := range users {
|
||||
userResponses = append(userResponses, user.ToResponse())
|
||||
}
|
||||
|
||||
return &UserListResponse{
|
||||
Users: userResponses,
|
||||
Meta: pagination.Response(total),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete deletes a user (hard delete)
|
||||
func (s *userService) Delete(ctx context.Context, id string) error {
|
||||
err := s.repository.Delete(ctx, id)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to delete user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return errors.New("failed to delete user")
|
||||
}
|
||||
|
||||
s.logger.Info("User deleted successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Login authenticates a user
|
||||
func (s *userService) Login(ctx context.Context, form *LoginForm) (*AuthResponse, error) {
|
||||
// Find user by email or username
|
||||
@@ -153,7 +304,7 @@ func (s *userService) Login(ctx context.Context, form *LoginForm) (*AuthResponse
|
||||
}
|
||||
|
||||
// Update last login
|
||||
err = s.repository.UpdateLastLogin(ctx, user.ID.Hex())
|
||||
err = s.repository.Login(ctx, user.ID.Hex())
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update last login", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -315,16 +466,6 @@ func (s *userService) ResetPassword(ctx context.Context, form *ResetPasswordForm
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserByID retrieves a user by ID
|
||||
func (s *userService) GetUserByID(ctx context.Context, id string) (*User, error) {
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetProfile retrieves the current user's profile
|
||||
func (s *userService) GetProfile(ctx context.Context, userID string) (*User, error) {
|
||||
user, err := s.repository.GetByID(ctx, userID)
|
||||
@@ -391,212 +532,8 @@ func (s *userService) UpdateProfile(ctx context.Context, userID string, form *Up
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUser updates user information
|
||||
func (s *userService) UpdateUser(ctx context.Context, id string, form *UpdateUserForm, updatedBy *string) (*User, error) {
|
||||
// Get user
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
|
||||
// Update fields if provided
|
||||
if form.FullName != nil {
|
||||
user.FullName = *form.FullName
|
||||
}
|
||||
|
||||
if form.Username != nil {
|
||||
// Check if username already exists
|
||||
existingUser, _ := s.repository.GetByUsername(ctx, *form.Username)
|
||||
if existingUser != nil && existingUser.ID.Hex() != id {
|
||||
return nil, errors.New("username already exists")
|
||||
}
|
||||
user.Username = *form.Username
|
||||
}
|
||||
|
||||
if form.Email != nil {
|
||||
// Check if email already exists
|
||||
existingUser, _ := s.repository.GetByEmail(ctx, *form.Email)
|
||||
if existingUser != nil && existingUser.ID.Hex() != id {
|
||||
return nil, errors.New("email already exists")
|
||||
}
|
||||
user.Email = *form.Email
|
||||
}
|
||||
|
||||
if form.Role != nil {
|
||||
role := UserRole(*form.Role)
|
||||
if !s.validator.IsValidRole(role) {
|
||||
return nil, errors.New("invalid role")
|
||||
}
|
||||
user.Role = role
|
||||
}
|
||||
|
||||
if form.Department != nil {
|
||||
user.Department = form.Department
|
||||
}
|
||||
|
||||
if form.Position != nil {
|
||||
user.Position = form.Position
|
||||
}
|
||||
|
||||
if form.Phone != nil {
|
||||
user.Phone = form.Phone
|
||||
}
|
||||
|
||||
if form.ProfileImage != nil {
|
||||
user.ProfileImage = form.ProfileImage
|
||||
}
|
||||
|
||||
if form.Status != nil {
|
||||
user.Status = UserStatus(*form.Status)
|
||||
}
|
||||
|
||||
// Set updated by
|
||||
user.UpdatedBy = updatedBy
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return nil, errors.New("failed to update user")
|
||||
}
|
||||
|
||||
s.logger.Info("User updated successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user (soft delete)
|
||||
func (s *userService) DeleteUser(ctx context.Context, id string, deletedBy *string) error {
|
||||
// Get user to check if exists
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// Set updated by before deletion
|
||||
user.UpdatedBy = deletedBy
|
||||
|
||||
// Update user status to inactive (soft delete)
|
||||
user.Status = UserStatusInactive
|
||||
err = s.repository.Update(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to delete user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return errors.New("failed to delete user")
|
||||
}
|
||||
|
||||
s.logger.Info("User deleted successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
"deleted_by": *deletedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListUsers lists users with search and filters
|
||||
func (s *userService) ListUsers(ctx context.Context, form *ListUsersForm) (*UserListResponse, error) {
|
||||
// Set defaults
|
||||
limit := 20
|
||||
if form.Limit != nil {
|
||||
limit = *form.Limit
|
||||
}
|
||||
|
||||
offset := 0
|
||||
if form.Offset != nil {
|
||||
offset = *form.Offset
|
||||
}
|
||||
|
||||
search := ""
|
||||
if form.Search != nil {
|
||||
search = *form.Search
|
||||
}
|
||||
|
||||
sortBy := "created_at"
|
||||
if form.SortBy != nil {
|
||||
sortBy = *form.SortBy
|
||||
}
|
||||
|
||||
sortOrder := "desc"
|
||||
if form.SortOrder != nil {
|
||||
sortOrder = *form.SortOrder
|
||||
}
|
||||
|
||||
// Parse company ID if provided
|
||||
var companyID *string
|
||||
if form.CompanyID != nil {
|
||||
companyID = form.CompanyID
|
||||
}
|
||||
|
||||
// Get users
|
||||
users, err := s.repository.Search(ctx, search, form.Status, form.Role, companyID, limit, offset, sortBy, sortOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list users", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, errors.New("failed to list users")
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
var userResponses []*UserResponse
|
||||
for _, user := range users {
|
||||
userResponses = append(userResponses, user.ToResponse())
|
||||
}
|
||||
|
||||
// TODO: Implement proper count for pagination metadata
|
||||
total := int64(len(users)) // This should be a separate count query
|
||||
totalPages := (total + int64(limit) - 1) / int64(limit)
|
||||
|
||||
return &UserListResponse{
|
||||
Users: userResponses,
|
||||
Total: total,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
TotalPages: int(totalPages),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateUserStatus updates user status
|
||||
func (s *userService) UpdateUserStatus(ctx context.Context, id string, form *UpdateUserStatusForm, updatedBy *string) error {
|
||||
// Get user
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
// Update status
|
||||
user.Status = UserStatus(form.Status)
|
||||
user.UpdatedBy = updatedBy
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update user status", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
"status": form.Status,
|
||||
})
|
||||
return errors.New("failed to update user status")
|
||||
}
|
||||
|
||||
s.logger.Info("User status updated successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
"status": form.Status,
|
||||
"updated_by": *updatedBy,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUserRole updates user role
|
||||
func (s *userService) UpdateUserRole(ctx context.Context, id string, form *UpdateUserRoleForm, updatedBy *string) error {
|
||||
func (s *userService) UpdateRole(ctx context.Context, id string, form *UpdateUserRoleForm) error {
|
||||
// Get user
|
||||
user, err := s.repository.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -611,7 +548,6 @@ func (s *userService) UpdateUserRole(ctx context.Context, id string, form *Updat
|
||||
|
||||
// Update role
|
||||
user.Role = role
|
||||
user.UpdatedBy = updatedBy
|
||||
|
||||
// Update in database
|
||||
err = s.repository.Update(ctx, user)
|
||||
@@ -625,52 +561,13 @@ func (s *userService) UpdateUserRole(ctx context.Context, id string, form *Updat
|
||||
}
|
||||
|
||||
s.logger.Info("User role updated successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
"role": form.Role,
|
||||
"updated_by": *updatedBy,
|
||||
"user_id": id,
|
||||
"role": form.Role,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUsersByCompanyID retrieves users by company ID with pagination
|
||||
func (s *userService) GetUsersByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*User, int64, error) {
|
||||
users, err := s.repository.GetByCompanyID(ctx, companyID, limit, offset)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get users by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return nil, 0, errors.New("failed to get users by company")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
total, err := s.repository.CountByCompanyID(ctx, companyID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to count users by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
})
|
||||
return users, 0, nil // Return users even if count fails
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// GetUsersByRole retrieves users by role with pagination
|
||||
func (s *userService) GetUsersByRole(ctx context.Context, role UserRole, limit, offset int) ([]*User, error) {
|
||||
users, err := s.repository.GetByRole(ctx, role, limit, offset)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get users by role", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"role": role,
|
||||
})
|
||||
return nil, errors.New("failed to get users by role")
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Logout logs out a user and invalidates tokens
|
||||
func (s *userService) Logout(ctx context.Context, userID string, accessToken string) error {
|
||||
// Invalidate the access token
|
||||
|
||||
Reference in New Issue
Block a user