// Package mutil provides utility functions for working with account-bound objects // with permission enforcement. // // Example usage: // // // Using the low-level repository approach // objects, err := mutil.GetAccountBoundObjects[model.ProjectFilter]( // ctx, logger, accountRef, orgRef, model.ActionRead, // repository.Query(), &model.ViewCursor{Limit: &limit, Offset: &offset, IsArchived: &isArchived}, // enforcer, repo) // // // Using the AccountBoundDB interface approach // objects, err := mutil.GetAccountBoundObjectsFromDB[model.ProjectFilter]( // ctx, logger, accountRef, orgRef, // repository.Query(), &model.ViewCursor{Limit: &limit, Offset: &offset, IsArchived: &isArchived}, // accountBoundDB) package mutil import ( "context" "errors" "github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" mutil "github.com/tech/sendico/pkg/mutil/db" "github.com/tech/sendico/pkg/mutil/mzap" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" ) // GetAccountBoundObjects retrieves account-bound objects with permission enforcement. // This function handles the complex logic of: // 1. Finding objects where accountRef matches OR is null/absent // 2. Checking organization-level permissions for each object // 3. Filtering to only objects the account has permission to read func GetAccountBoundObjects[T any]( ctx context.Context, logger mlogger.Logger, accountRef, organizationRef primitive.ObjectID, filter builder.Query, cursor *model.ViewCursor, enforcer auth.Enforcer, repo repository.Repository, ) ([]T, error) { // Build query to find objects where accountRef matches OR is null/absent accountQuery := repository.WithOrg(accountRef, organizationRef) // Get all account-bound objects that match the criteria allObjects, err := repo.ListAccountBound(ctx, repository.ApplyCursor(accountQuery, cursor)) if err != nil { if !errors.Is(err, merrors.ErrNoData) { logger.Warn("Failed to fetch account bound objects", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef), ) } else { logger.Debug("No matching account bound objects found", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef), ) } return nil, err } if len(allObjects) == 0 { return nil, merrors.NoData("no_account_bound_objects_found") } allowed := make([]primitive.ObjectID, 0, len(allObjects)) for _, ref := range allObjects { allowed = append(allowed, *ref.GetID()) } if len(allowed) == 0 { return nil, merrors.NoData("no_data_found_or_allowed") } logger.Debug("Successfully retrieved account bound objects", zap.Int("total_count", len(allObjects)), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("organization_ref", organizationRef), zap.Any("objs", allObjects), ) return mutil.GetObjects[T](ctx, logger, repository.Query().In(repository.IDField(), allowed), cursor, repo) }