08cf927294
- Updated User entity to embed MongoDB model and replace uuid.UUID with string for ID fields. - Modified repository methods to accept string IDs instead of uuid.UUID, enhancing compatibility with MongoDB. - Refactored service and handler methods to align with the new ID type, ensuring consistent usage across the user management functionality. - Improved response handling in UserListResponse to directly use string IDs. - Streamlined index creation in the user repository using the MongoDB ORM for better maintainability.
404 lines
10 KiB
Go
404 lines
10 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
"tm/pkg/logger"
|
|
mongopkg "tm/pkg/mongo"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
)
|
|
|
|
// Repository defines methods for user data access
|
|
type Repository interface {
|
|
Create(ctx context.Context, user *User) 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)
|
|
}
|
|
|
|
// userRepository implements the Repository interface using the MongoDB ORM
|
|
type userRepository struct {
|
|
ormRepo mongopkg.Repository[User]
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewUserRepository creates a new user repository
|
|
func NewUserRepository(mongoManager *mongopkg.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"),
|
|
}
|
|
|
|
// Create indexes
|
|
err := mongoManager.CreateIndexes("users", indexes)
|
|
if err != nil {
|
|
logger.Warn("Failed to create user indexes", map[string]interface{}{
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
// Create ORM repository
|
|
ormRepo := mongopkg.NewRepository[User](mongoManager.GetCollection("users"), logger)
|
|
|
|
return &userRepository{
|
|
ormRepo: ormRepo,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
// Get user first
|
|
user, err := r.GetByID(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update last login timestamp
|
|
user.LastLoginAt = &[]int64{time.Now().Unix()}[0]
|
|
user.SetUpdatedAt(time.Now().Unix())
|
|
|
|
// Update in database
|
|
err = r.ormRepo.Update(ctx, user)
|
|
if err != nil {
|
|
r.logger.Error("Failed to update last login", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"user_id": id,
|
|
})
|
|
return err
|
|
}
|
|
|
|
r.logger.Info("Last login updated successfully", map[string]interface{}{
|
|
"user_id": id,
|
|
})
|
|
|
|
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},
|
|
}
|
|
|
|
count, err := r.ormRepo.Count(ctx, filter)
|
|
if err != nil {
|
|
r.logger.Error("Failed to count users by company ID", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"company_id": companyID,
|
|
})
|
|
return 0, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|