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:
+189
-326
@@ -5,44 +5,35 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
mongopkg "tm/pkg/mongo"
|
||||
orm "tm/pkg/mongo"
|
||||
"tm/pkg/response"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Repository defines methods for user data access
|
||||
type Repository interface {
|
||||
Login(ctx context.Context, id string) error
|
||||
Create(ctx context.Context, user *User) error
|
||||
Update(ctx context.Context, user *User) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
GetByID(ctx context.Context, id string) (*User, error)
|
||||
GetByEmail(ctx context.Context, email string) (*User, error)
|
||||
GetByUsername(ctx context.Context, username string) (*User, error)
|
||||
Update(ctx context.Context, user *User) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
List(ctx context.Context, limit, offset int) ([]*User, error)
|
||||
Search(ctx context.Context, search string, status *string, role *string, companyID *string, limit, offset int, sortBy, sortOrder string) ([]*User, error)
|
||||
GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*User, error)
|
||||
GetByRole(ctx context.Context, role UserRole, limit, offset int) ([]*User, error)
|
||||
UpdateLastLogin(ctx context.Context, id string) error
|
||||
CountByCompanyID(ctx context.Context, companyID string) (int64, error)
|
||||
Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) ([]User, int64, error)
|
||||
}
|
||||
|
||||
// userRepository implements the Repository interface using the MongoDB ORM
|
||||
type userRepository struct {
|
||||
ormRepo mongopkg.Repository[User]
|
||||
ormRepo orm.Repository[User]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewUserRepository creates a new user repository
|
||||
func NewUserRepository(mongoManager *mongopkg.ConnectionManager, logger logger.Logger) Repository {
|
||||
func NewUserRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository {
|
||||
// Create indexes using the ORM's index management
|
||||
indexes := []mongopkg.Index{
|
||||
*mongopkg.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
|
||||
*mongopkg.CreateUniqueIndex("username_idx", bson.D{{Key: "username", Value: 1}}),
|
||||
*mongopkg.NewIndex("company_id_idx", bson.D{{Key: "company_id", Value: 1}}),
|
||||
*mongopkg.NewIndex("role_idx", bson.D{{Key: "role", Value: 1}}),
|
||||
*mongopkg.NewIndex("status_idx", bson.D{{Key: "status", Value: 1}}),
|
||||
*mongopkg.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*mongopkg.CreateTextIndex("search_idx", "full_name", "email", "username", "department", "position"),
|
||||
indexes := []orm.Index{
|
||||
*orm.CreateTextIndex("search_idx", "full_name", "email", "username"),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
@@ -54,7 +45,7 @@ func NewUserRepository(mongoManager *mongopkg.ConnectionManager, logger logger.L
|
||||
}
|
||||
|
||||
// Create ORM repository
|
||||
ormRepo := mongopkg.NewRepository[User](mongoManager.GetCollection("users"), logger)
|
||||
ormRepo := orm.NewRepository[User](mongoManager.GetCollection("users"), logger)
|
||||
|
||||
return &userRepository{
|
||||
ormRepo: ormRepo,
|
||||
@@ -62,300 +53,8 @@ func NewUserRepository(mongoManager *mongopkg.ConnectionManager, logger logger.L
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new user
|
||||
func (r *userRepository) Create(ctx context.Context, user *User) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
user.SetCreatedAt(now)
|
||||
user.SetUpdatedAt(now)
|
||||
|
||||
// Use ORM to create user
|
||||
err := r.ormRepo.Create(ctx, user)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": user.Email,
|
||||
"user_id": user.ID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("User created successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a user by ID
|
||||
func (r *userRepository) GetByID(ctx context.Context, id string) (*User, error) {
|
||||
user, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a user by email
|
||||
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
|
||||
filter := bson.M{"email": email}
|
||||
user, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by email", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": email,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetByUsername retrieves a user by username
|
||||
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
|
||||
filter := bson.M{"username": username}
|
||||
user, err := r.ormRepo.FindOne(ctx, filter)
|
||||
if err != nil {
|
||||
if errors.Is(err, mongopkg.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by username", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"username": username,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Update updates a user
|
||||
func (r *userRepository) Update(ctx context.Context, user *User) error {
|
||||
// Set updated timestamp using Unix timestamp
|
||||
user.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Use ORM to update user
|
||||
err := r.ormRepo.Update(ctx, user)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": user.ID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("User updated successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a user (soft delete by setting status to inactive)
|
||||
func (r *userRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get user first
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update status to inactive
|
||||
user.Status = UserStatusInactive
|
||||
user.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Update in database
|
||||
err = r.ormRepo.Update(ctx, user)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("User deleted successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves users with pagination
|
||||
func (r *userRepository) List(ctx context.Context, limit, offset int) ([]*User, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
// Only active users by default
|
||||
filter := bson.M{"status": bson.M{"$ne": UserStatusInactive}}
|
||||
|
||||
// Use ORM to find users
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to list users", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert []User to []*User
|
||||
users := make([]*User, len(result.Items))
|
||||
for i := range result.Items {
|
||||
users[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Search retrieves users with search and filters
|
||||
func (r *userRepository) Search(ctx context.Context, search string, status *string, role *string, companyID *string, limit, offset int, sortBy, sortOrder string) ([]*User, error) {
|
||||
// Build filter
|
||||
filter := bson.M{}
|
||||
|
||||
if search != "" {
|
||||
filter["$text"] = bson.M{"$search": search}
|
||||
}
|
||||
|
||||
if status != nil {
|
||||
filter["status"] = *status
|
||||
}
|
||||
|
||||
if role != nil {
|
||||
filter["role"] = *role
|
||||
}
|
||||
|
||||
if companyID != nil {
|
||||
filter["company_id"] = *companyID
|
||||
}
|
||||
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset)
|
||||
|
||||
// Set sort
|
||||
if sortBy != "" {
|
||||
if sortOrder == "desc" {
|
||||
pagination.SortDesc(sortBy)
|
||||
} else {
|
||||
pagination.SortAsc(sortBy)
|
||||
}
|
||||
} else {
|
||||
pagination.SortDesc("created_at") // Default sort
|
||||
}
|
||||
|
||||
// Use ORM to find users
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination.Build())
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to search users", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"search": search,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert []User to []*User
|
||||
users := make([]*User, len(result.Items))
|
||||
for i := range result.Items {
|
||||
users[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetByCompanyID retrieves users by company ID with pagination
|
||||
func (r *userRepository) GetByCompanyID(ctx context.Context, companyID string, limit, offset int) ([]*User, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
// Filter by company ID and active status
|
||||
filter := bson.M{
|
||||
"company_id": companyID,
|
||||
"status": bson.M{"$ne": UserStatusInactive},
|
||||
}
|
||||
|
||||
// Use ORM to find users
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get users by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert []User to []*User
|
||||
users := make([]*User, len(result.Items))
|
||||
for i := range result.Items {
|
||||
users[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// GetByRole retrieves users by role with pagination
|
||||
func (r *userRepository) GetByRole(ctx context.Context, role UserRole, limit, offset int) ([]*User, error) {
|
||||
// Build pagination
|
||||
pagination := mongopkg.NewPaginationBuilder().
|
||||
Limit(limit).
|
||||
Skip(offset).
|
||||
SortDesc("created_at").
|
||||
Build()
|
||||
|
||||
// Filter by role and active status
|
||||
filter := bson.M{
|
||||
"role": role,
|
||||
"status": bson.M{"$ne": UserStatusInactive},
|
||||
}
|
||||
|
||||
// Use ORM to find users
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get users by role", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"role": role,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert []User to []*User
|
||||
users := make([]*User, len(result.Items))
|
||||
for i := range result.Items {
|
||||
users[i] = &result.Items[i]
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// UpdateLastLogin updates the last login timestamp
|
||||
func (r *userRepository) UpdateLastLogin(ctx context.Context, id string) error {
|
||||
// Login updates the last login timestamp
|
||||
func (r *userRepository) Login(ctx context.Context, id string) error {
|
||||
// Get user first
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
@@ -383,21 +82,185 @@ func (r *userRepository) UpdateLastLogin(ctx context.Context, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountByCompanyID counts users by company ID
|
||||
func (r *userRepository) CountByCompanyID(ctx context.Context, companyID string) (int64, error) {
|
||||
filter := bson.M{
|
||||
"company_id": companyID,
|
||||
"status": bson.M{"$ne": UserStatusInactive},
|
||||
}
|
||||
// Create creates a new user
|
||||
func (r *userRepository) Create(ctx context.Context, user *User) error {
|
||||
// Set created timestamp using Unix timestamp
|
||||
now := time.Now().Unix()
|
||||
user.SetCreatedAt(now)
|
||||
|
||||
count, err := r.ormRepo.Count(ctx, filter)
|
||||
// Use ORM to create user
|
||||
err := r.ormRepo.Create(ctx, user)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to count users by company ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"company_id": companyID,
|
||||
r.logger.Error("Failed to create user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": user.Email,
|
||||
"user_id": user.ID,
|
||||
})
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
r.logger.Info("User created successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates a user
|
||||
func (r *userRepository) Update(ctx context.Context, user *User) error {
|
||||
// Set updated timestamp using Unix timestamp
|
||||
user.SetUpdatedAt(time.Now().Unix())
|
||||
|
||||
// Use ORM to update user
|
||||
err := r.ormRepo.Update(ctx, user)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": user.ID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("User updated successfully", map[string]interface{}{
|
||||
"user_id": user.ID,
|
||||
"email": user.Email,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a user by ID
|
||||
func (r *userRepository) GetByID(ctx context.Context, id string) (*User, error) {
|
||||
user, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by ID", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a user by email
|
||||
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
|
||||
user, err := r.ormRepo.FindOne(ctx, bson.M{"email": email})
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by email", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": email,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetByUsername retrieves a user by username
|
||||
func (r *userRepository) GetByUsername(ctx context.Context, username string) (*User, error) {
|
||||
user, err := r.ormRepo.FindOne(ctx, bson.M{"username": username})
|
||||
if err != nil {
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
r.logger.Error("Failed to get user by username", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"username": username,
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Delete deletes a user (soft delete by setting status to inactive)
|
||||
func (r *userRepository) Delete(ctx context.Context, id string) error {
|
||||
// Get user first
|
||||
_, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
if errors.Is(err, orm.ErrDocumentNotFound) {
|
||||
return errors.New("user not found")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete in database
|
||||
err = r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete user", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"user_id": id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("User deleted successfully", map[string]interface{}{
|
||||
"user_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List retrieves users with pagination
|
||||
func (r *userRepository) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) ([]User, int64, error) {
|
||||
filter := bson.M{}
|
||||
|
||||
if form.Search != nil {
|
||||
filter["$or"] = bson.A{
|
||||
bson.M{"full_name": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
bson.M{"email": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
bson.M{"username": bson.M{"$regex": form.Search, "$options": "i"}},
|
||||
}
|
||||
}
|
||||
|
||||
if form.Status != nil {
|
||||
filter["status"] = form.Status
|
||||
}
|
||||
|
||||
if form.Role != nil {
|
||||
filter["role"] = form.Role
|
||||
}
|
||||
|
||||
// Build sort
|
||||
sort := "created_at"
|
||||
if pagination.SortBy != "" {
|
||||
sort = pagination.SortBy
|
||||
}
|
||||
|
||||
sortOrder := "desc"
|
||||
if pagination.SortOrder != "" {
|
||||
sortOrder = pagination.SortOrder
|
||||
}
|
||||
|
||||
// Build pagination
|
||||
paginationBuilder := orm.NewPaginationBuilder().
|
||||
Limit(pagination.Limit).
|
||||
Skip(pagination.Offset).
|
||||
SortBy(sort, sortOrder).
|
||||
Build()
|
||||
|
||||
// Use ORM to find users
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, paginationBuilder)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to list users", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return result.Items, result.TotalCount, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user