package indexable import ( "context" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" ) // IndexableDB implements db.IndexableDB interface with generic support type IndexableDB[T storable.Storable] struct { repo repository.Repository logger mlogger.Logger createEmpty func() T getIndexable func(T) *model.Indexable } // NewIndexableDB creates a new IndexableDB instance func NewIndexableDB[T storable.Storable]( repo repository.Repository, logger mlogger.Logger, createEmpty func() T, getIndexable func(T) *model.Indexable, ) *IndexableDB[T] { return &IndexableDB[T]{ repo: repo, logger: logger, createEmpty: createEmpty, getIndexable: getIndexable, } } // Reorder implements the db.IndexableDB interface with single filter parameter func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef primitive.ObjectID, newIndex int, filter builder.Query) error { // Get current object to find its index obj := db.createEmpty() err := db.repo.Get(ctx, objectRef, obj) if err != nil { db.logger.Error("Failed to get object for reordering", zap.Error(err), zap.String("object_ref", objectRef.Hex()), zap.Int("new_index", newIndex)) return err } // Extract index from the object indexable := db.getIndexable(obj) currentIndex := indexable.Index if currentIndex == newIndex { db.logger.Debug("No reordering needed - same index", zap.String("object_ref", objectRef.Hex()), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex)) return nil // No change needed } // Simple reordering logic if currentIndex < newIndex { // Moving down: shift items between currentIndex+1 and newIndex up by -1 patch := repository.Patch().Inc(repository.IndexField(), -1) reorderFilter := filter. And(repository.IndexOpFilter(currentIndex+1, builder.Gte)). And(repository.IndexOpFilter(newIndex, builder.Lte)) updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch) if err != nil { db.logger.Error("Failed to shift objects during reordering (moving down)", zap.Error(err), zap.String("object_ref", objectRef.Hex()), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount)) return err } db.logger.Debug("Successfully shifted objects (moving down)", zap.String("object_ref", objectRef.Hex()), zap.Int("updated_count", updatedCount)) } else { // Moving up: shift items between newIndex and currentIndex-1 down by +1 patch := repository.Patch().Inc(repository.IndexField(), 1) reorderFilter := filter. And(repository.IndexOpFilter(newIndex, builder.Gte)). And(repository.IndexOpFilter(currentIndex-1, builder.Lte)) updatedCount, err := db.repo.PatchMany(ctx, reorderFilter, patch) if err != nil { db.logger.Error("Failed to shift objects during reordering (moving up)", zap.Error(err), zap.String("object_ref", objectRef.Hex()), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex), zap.Int("updated_count", updatedCount)) return err } db.logger.Debug("Successfully shifted objects (moving up)", zap.String("object_ref", objectRef.Hex()), zap.Int("updated_count", updatedCount)) } // Update the target object to new index patch := repository.Patch().Set(repository.IndexField(), newIndex) err = db.repo.Patch(ctx, objectRef, patch) if err != nil { db.logger.Error("Failed to update target object index", zap.Error(err), zap.String("object_ref", objectRef.Hex()), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex)) return err } db.logger.Info("Successfully reordered object", zap.String("object_ref", objectRef.Hex()), zap.Int("old_index", currentIndex), zap.Int("new_index", newIndex)) return nil }