183 lines
8.1 KiB
Go
183 lines
8.1 KiB
Go
package auth
|
|
|
|
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/merrors"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mutil/mzap"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// IndexableDB implements reordering with permission checking
|
|
type indexableDBImp[T storable.Storable] struct {
|
|
repo repository.Repository
|
|
logger mlogger.Logger
|
|
enforcer Enforcer
|
|
createEmpty func() T
|
|
getIndexable func(T) *model.Indexable
|
|
}
|
|
|
|
// NewIndexableDB creates a new auth.IndexableDB instance
|
|
func newIndexableDBImp[T storable.Storable](
|
|
repo repository.Repository,
|
|
logger mlogger.Logger,
|
|
enforcer Enforcer,
|
|
createEmpty func() T,
|
|
getIndexable func(T) *model.Indexable,
|
|
) IndexableDB[T] {
|
|
return &indexableDBImp[T]{
|
|
repo: repo,
|
|
logger: logger.Named("indexable"),
|
|
enforcer: enforcer,
|
|
createEmpty: createEmpty,
|
|
getIndexable: getIndexable,
|
|
}
|
|
}
|
|
|
|
// Reorder implements reordering with permission checking using EnforceBatch
|
|
func (db *indexableDBImp[T]) Reorder(ctx context.Context, accountRef, objectRef primitive.ObjectID, newIndex int, filter builder.Query) error {
|
|
// Get current object to find its index
|
|
obj := db.createEmpty()
|
|
if err := db.repo.Get(ctx, objectRef, obj); err != nil {
|
|
db.logger.Warn("Failed to get object for reordering", zap.Error(err), zap.Int("new_index", newIndex),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
|
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", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
|
return nil // No change needed
|
|
}
|
|
|
|
// Determine which objects will be affected by the reordering
|
|
var affectedObjects []model.PermissionBoundStorable
|
|
|
|
if currentIndex < newIndex {
|
|
// Moving down: items between currentIndex+1 and newIndex will be shifted up by -1
|
|
reorderFilter := filter.
|
|
And(repository.IndexOpFilter(currentIndex+1, builder.Gte)).
|
|
And(repository.IndexOpFilter(newIndex, builder.Lte))
|
|
|
|
// Get all affected objects using ListPermissionBound
|
|
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
|
if err != nil {
|
|
db.logger.Warn("Failed to get affected objects for reordering (moving down)",
|
|
zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
|
return err
|
|
}
|
|
affectedObjects = append(affectedObjects, objects...)
|
|
db.logger.Debug("Found affected objects for moving down",
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
|
} else {
|
|
// Moving up: items between newIndex and currentIndex-1 will be shifted down by +1
|
|
reorderFilter := filter.
|
|
And(repository.IndexOpFilter(newIndex, builder.Gte)).
|
|
And(repository.IndexOpFilter(currentIndex-1, builder.Lte))
|
|
|
|
// Get all affected objects using ListPermissionBound
|
|
objects, err := db.repo.ListPermissionBound(ctx, reorderFilter)
|
|
if err != nil {
|
|
db.logger.Warn("Failed to get affected objects for reordering (moving up)", zap.Error(err),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
|
return err
|
|
}
|
|
affectedObjects = append(affectedObjects, objects...)
|
|
db.logger.Debug("Found affected objects for moving up", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(objects)))
|
|
}
|
|
|
|
// Add the target object to the list of objects that need permission checking
|
|
targetObjects, err := db.repo.ListPermissionBound(ctx, repository.IDFilter(objectRef))
|
|
if err != nil {
|
|
db.logger.Warn("Failed to get target object for permission checking", zap.Error(err),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef))
|
|
return err
|
|
}
|
|
if len(targetObjects) > 0 {
|
|
affectedObjects = append(affectedObjects, targetObjects[0])
|
|
}
|
|
|
|
// Check permissions for all affected objects using EnforceBatch
|
|
db.logger.Debug("Checking permissions for reordering", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)),
|
|
zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
|
|
|
permissions, err := db.enforcer.EnforceBatch(ctx, affectedObjects, accountRef, model.ActionUpdate)
|
|
if err != nil {
|
|
db.logger.Warn("Failed to check permissions for reordering", zap.Error(err),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("affected_count", len(affectedObjects)))
|
|
return merrors.Internal("failed to check permissions for reordering")
|
|
}
|
|
|
|
// Verify all objects have update permission
|
|
for resObjectRef, hasPermission := range permissions {
|
|
if !hasPermission {
|
|
db.logger.Info("Permission denied for object during reordering", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionUpdate)))
|
|
return merrors.AccessDenied(db.repo.Collection(), string(model.ActionUpdate), resObjectRef)
|
|
}
|
|
}
|
|
|
|
db.logger.Debug("All permissions granted, proceeding with reordering", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("permission_count", len(permissions)))
|
|
|
|
// All permissions checked, proceed with reordering
|
|
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.Warn("Failed to shift objects during reordering (moving down)", zap.Error(err),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
|
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)", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), 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.Warn("Failed to shift objects during reordering (moving up)", zap.Error(err),
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef),
|
|
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)", mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("updated_count", updatedCount))
|
|
}
|
|
|
|
// Update the target object to new index
|
|
if err := db.repo.Patch(ctx, objectRef, repository.Patch().Set(repository.IndexField(), newIndex)); err != nil {
|
|
db.logger.Warn("Failed to update target object index", zap.Error(err), mzap.ObjRef("account_ref", accountRef),
|
|
mzap.ObjRef("object_ref", objectRef), zap.Int("current_index", currentIndex), zap.Int("new_index", newIndex))
|
|
return err
|
|
}
|
|
|
|
db.logger.Debug("Successfully reordered object with permission checking",
|
|
mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Int("old_index", currentIndex),
|
|
zap.Int("new_index", newIndex), zap.Int("affected_count", len(affectedObjects)))
|
|
return nil
|
|
}
|