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 }