package user import ( "context" "errors" "time" "tm/pkg/logger" orm "tm/pkg/mongo" "tm/pkg/response" "go.mongodb.org/mongo-driver/v2/bson" mongopkg "go.mongodb.org/mongo-driver/v2/mongo" ) // 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) GetByIDs(ctx context.Context, userIDs []string) ([]User, error) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], error) } // userRepository implements the Repository interface using the MongoDB ORM type userRepository struct { ormRepo orm.Repository[User] collection *mongopkg.Collection logger logger.Logger } // NewUserRepository creates a new user repository func NewRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) Repository { // Create indexes using the ORM's index management indexes := []orm.Index{ *orm.CreateTextIndex("search_idx", "full_name", "email", "username"), } // 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 collection := mongoManager.GetCollection("users") ormRepo := orm.NewRepository[User](collection, logger) return &userRepository{ ormRepo: ormRepo, collection: collection, logger: logger, } } // 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 { 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 } // 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) // 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 } // 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 { // Backward compatibility for legacy records with string _id. legacyFilterID := user.ID.Hex() updateDoc := bson.M{ "$set": bson.M{ "full_name": user.FullName, "username": user.Username, "email": user.Email, "password": user.Password, "role": user.Role, "status": user.Status, "department": user.Department, "position": user.Position, "phone": user.Phone, "profile_image": user.ProfileImage, "device_token": user.DeviceToken, "is_verified": user.IsVerified, "last_login_at": user.LastLoginAt, "updated_at": user.UpdatedAt, "updated_by": user.UpdatedBy, }, } legacyResult, legacyErr := r.collection.UpdateOne(ctx, bson.M{"_id": legacyFilterID}, updateDoc) if legacyErr == nil && legacyResult.MatchedCount > 0 { r.logger.Info("User updated successfully using legacy string ID", map[string]interface{}{ "user_id": legacyFilterID, "email": user.Email, }) return 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) { // Backward compatibility: some legacy seed data may store _id as string instead of ObjectID. legacyUser, legacyErr := r.ormRepo.FindOne(ctx, bson.M{"_id": id}) if legacyErr == nil { return legacyUser, nil } if errors.Is(legacyErr, orm.ErrDocumentNotFound) { return nil, errors.New("user not found") } r.logger.Error("Failed to get user by legacy string ID", map[string]interface{}{ "error": legacyErr.Error(), "user_id": id, }) return nil, legacyErr } 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 } // GetByIDs retrieves users by IDs func (r *userRepository) GetByIDs(ctx context.Context, userIDs []string) ([]User, error) { objectIDs := make([]bson.ObjectID, 0) for _, id := range userIDs { objectID, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } objectIDs = append(objectIDs, objectID) } users, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(userIDs)).Build()) if err != nil { return nil, err } return users.Items, nil } // Search retrieves users with pagination func (r *userRepository) Search(ctx context.Context, form *SearchUsersForm, pagination *response.Pagination) (*orm.PaginatedResult[User], 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 } mongoPagination, err := orm.BuildListPagination( pagination.Limit, pagination.Offset, pagination.Cursor, pagination.SortBy, pagination.SortOrder, filter, orm.ListPaginationOptions{}, ) if err != nil { return nil, err } result, err := r.ormRepo.FindAll(ctx, filter, mongoPagination) if err != nil { r.logger.Error("Failed to list users", map[string]interface{}{ "error": err.Error(), }) return nil, err } return result, nil }