github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/bundlereferences/repository.go (about)

     1  package bundlereferences
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/pkg/log"
     7  
     8  	"github.com/kyma-incubator/compass/components/director/pkg/scope"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/internal/model"
    11  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    12  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    14  )
    15  
    16  const (
    17  	// BundleReferenceTable represents the db name of the BundleReference table
    18  	BundleReferenceTable string = `public.bundle_references`
    19  	// APIDefTable represents the db name of the API Definitions table
    20  	APIDefTable string = `api_definitions`
    21  	// EventDefTable represents the db name of the Event Definitions table
    22  	EventDefTable string = `event_api_definitions`
    23  
    24  	// APIDefIDColumn represents the db column of the APIDefinition ID
    25  	APIDefIDColumn string = "api_def_id"
    26  	// APIDefURLColumn represents the db column of the APIDefinition default url
    27  	APIDefURLColumn string = "api_def_url"
    28  	// EventDefIDColumn represents the db column of the EventDefinition ID
    29  	EventDefIDColumn string = "event_def_id"
    30  
    31  	bundleIDColumn          string = "bundle_id"
    32  	visibilityColumn        string = "visibility"
    33  	internalVisibilityScope string = "internal_visibility:read"
    34  	publicVisibilityValue   string = "public"
    35  )
    36  
    37  var (
    38  	bundleReferencesColumns         = []string{"api_def_id", "event_def_id", "bundle_id", "api_def_url", "id", "is_default_bundle"}
    39  	updatableColumns                = []string{"api_def_id", "event_def_id", "bundle_id", "api_def_url", "is_default_bundle"}
    40  	updatableColumnsWithoutBundleID = []string{"api_def_id", "event_def_id", "api_def_url"}
    41  )
    42  
    43  // BundleReferenceConverter converts BundleReferences between the model.BundleReference service-layer representation and the repo-layer representation Entity.
    44  //go:generate mockery --name=BundleReferenceConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    45  type BundleReferenceConverter interface {
    46  	ToEntity(in model.BundleReference) Entity
    47  	FromEntity(in Entity) (model.BundleReference, error)
    48  }
    49  
    50  type repository struct {
    51  	creator            repo.CreatorGlobal
    52  	unionLister        repo.UnionListerGlobal
    53  	lister             repo.ListerGlobal
    54  	getter             repo.SingleGetterGlobal
    55  	deleter            repo.DeleterGlobal
    56  	updater            repo.UpdaterGlobal
    57  	queryBuilderAPIs   repo.QueryBuilderGlobal
    58  	queryBuilderEvents repo.QueryBuilderGlobal
    59  	conv               BundleReferenceConverter
    60  }
    61  
    62  // NewRepository returns a new entity responsible for repo-layer BundleReference operations.
    63  func NewRepository(conv BundleReferenceConverter) *repository {
    64  	return &repository{
    65  		creator:            repo.NewCreatorGlobal(resource.BundleReference, BundleReferenceTable, bundleReferencesColumns),
    66  		unionLister:        repo.NewUnionListerGlobal(resource.BundleReference, BundleReferenceTable, []string{}),
    67  		lister:             repo.NewListerGlobal(resource.BundleReference, BundleReferenceTable, bundleReferencesColumns),
    68  		getter:             repo.NewSingleGetterGlobal(resource.BundleReference, BundleReferenceTable, bundleReferencesColumns),
    69  		deleter:            repo.NewDeleterGlobal(resource.BundleReference, BundleReferenceTable),
    70  		updater:            repo.NewUpdaterGlobal(resource.BundleReference, BundleReferenceTable, updatableColumns, []string{}),
    71  		queryBuilderAPIs:   repo.NewQueryBuilderGlobal(resource.API, APIDefTable, []string{"id"}),
    72  		queryBuilderEvents: repo.NewQueryBuilderGlobal(resource.EventDefinition, EventDefTable, []string{"id"}),
    73  		conv:               conv,
    74  	}
    75  }
    76  
    77  // BundleReferencesCollection is an array of Entities
    78  type BundleReferencesCollection []Entity
    79  
    80  // Len returns the length of the collection
    81  func (r BundleReferencesCollection) Len() int {
    82  	return len(r)
    83  }
    84  
    85  // GetByID retrieves the BundleReference with matching objectID/objectID and bundleID from the Compass storage.
    86  func (r *repository) GetByID(ctx context.Context, objectType model.BundleReferenceObjectType, objectID, bundleID *string) (*model.BundleReference, error) {
    87  	fieldName, err := r.referenceObjectFieldName(objectType)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	var bundleReferenceEntity Entity
    93  	var conditions repo.Conditions
    94  
    95  	if bundleID == nil {
    96  		conditions = repo.Conditions{repo.NewEqualCondition(fieldName, objectID)}
    97  	} else {
    98  		conditions = repo.Conditions{
    99  			repo.NewEqualCondition(fieldName, objectID),
   100  			repo.NewEqualCondition(bundleIDColumn, bundleID),
   101  		}
   102  	}
   103  	err = r.getter.GetGlobal(ctx, conditions, repo.NoOrderBy, &bundleReferenceEntity)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  
   108  	bundleReferenceModel, err := r.conv.FromEntity(bundleReferenceEntity)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return &bundleReferenceModel, nil
   114  }
   115  
   116  // GetBundleIDsForObject retrieves all BundleReference IDs for matching objectID from the Compass storage.
   117  func (r *repository) GetBundleIDsForObject(ctx context.Context, objectType model.BundleReferenceObjectType, objectID *string) (ids []string, err error) {
   118  	fieldName, err := r.referenceObjectFieldName(objectType)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	var objectBundleIDs IDs
   124  
   125  	lister := r.lister.Clone()
   126  	lister.SetSelectedColumns([]string{bundleIDColumn})
   127  
   128  	conditions := repo.Conditions{
   129  		repo.NewEqualCondition(fieldName, objectID),
   130  	}
   131  
   132  	err = lister.ListGlobal(ctx, &objectBundleIDs, conditions...)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	return objectBundleIDs, nil
   138  }
   139  
   140  // Create adds the provided BundleReference into the Compass storage.
   141  func (r *repository) Create(ctx context.Context, item *model.BundleReference) error {
   142  	if item == nil {
   143  		return apperrors.NewInternalError("item can not be empty")
   144  	}
   145  
   146  	entity := r.conv.ToEntity(*item)
   147  
   148  	return r.creator.Create(ctx, entity)
   149  }
   150  
   151  // Update updates the provided BundleReference.
   152  func (r *repository) Update(ctx context.Context, item *model.BundleReference) error {
   153  	if item == nil {
   154  		return apperrors.NewInternalError("item cannot be nil")
   155  	}
   156  
   157  	fieldName, err := r.referenceObjectFieldName(item.ObjectType)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	updater := r.updater.Clone()
   163  
   164  	idColumns := make([]string, 0)
   165  	if item.BundleID == nil {
   166  		idColumns = append(idColumns, fieldName)
   167  		updater.SetUpdatableColumns(updatableColumnsWithoutBundleID)
   168  	} else {
   169  		idColumns = append(idColumns, fieldName, bundleIDColumn)
   170  	}
   171  
   172  	updater.SetIDColumns(idColumns)
   173  
   174  	entity := r.conv.ToEntity(*item)
   175  
   176  	return updater.UpdateSingleGlobal(ctx, entity)
   177  }
   178  
   179  // DeleteByReferenceObjectID removes a BundleReference with matching objectID and bundleID from the Compass storage.
   180  func (r *repository) DeleteByReferenceObjectID(ctx context.Context, bundleID string, objectType model.BundleReferenceObjectType, objectID string) error {
   181  	fieldName, err := r.referenceObjectFieldName(objectType)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	conditions := repo.Conditions{
   187  		repo.NewEqualCondition(fieldName, objectID),
   188  		repo.NewEqualCondition(bundleIDColumn, bundleID),
   189  	}
   190  
   191  	return r.deleter.DeleteOneGlobal(ctx, conditions)
   192  }
   193  
   194  // ListByBundleIDs retrieves all BundleReferences matching an array of bundleIDs from the Compass storage.
   195  func (r *repository) ListByBundleIDs(ctx context.Context, objectType model.BundleReferenceObjectType, bundleIDs []string, pageSize int, cursor string) ([]*model.BundleReference, map[string]int, error) {
   196  	objectTable, objectIDCol, columns, err := getDetailsByObjectType(objectType)
   197  	if err != nil {
   198  		return nil, nil, err
   199  	}
   200  
   201  	unionLister := r.unionLister.Clone()
   202  	unionLister.SetSelectedColumns(columns)
   203  
   204  	objectFieldName, err := r.referenceObjectFieldName(objectType)
   205  	if err != nil {
   206  		return nil, nil, err
   207  	}
   208  
   209  	isInternalVisibilityScopePresent, err := scope.Contains(ctx, internalVisibilityScope)
   210  	if err != nil {
   211  		log.C(ctx).Infof("No scopes are present in the context meaning the flow is not user-initiated. Processing %ss without visibility check...", objectType)
   212  		isInternalVisibilityScopePresent = true
   213  	}
   214  
   215  	queryBuilder := r.queryBuilderAPIs
   216  	if objectTable == EventDefTable {
   217  		queryBuilder = r.queryBuilderEvents
   218  	}
   219  
   220  	var conditions repo.Conditions
   221  	if !isInternalVisibilityScopePresent {
   222  		log.C(ctx).Infof("No internal visibility scope is present in the context. Processing only public %ss...", objectType)
   223  
   224  		query, args, err := queryBuilder.BuildQueryGlobal(false, repo.NewEqualCondition(visibilityColumn, publicVisibilityValue))
   225  		if err != nil {
   226  			return nil, nil, err
   227  		}
   228  		conditions = append(conditions, repo.NewInConditionForSubQuery(objectIDCol, query, args))
   229  	}
   230  
   231  	log.C(ctx).Infof("Internal visibility scope is present in the context. Processing %ss without visibility check...", objectType)
   232  	conditions = append(conditions, repo.NewNotNullCondition(objectFieldName))
   233  
   234  	orderByColumns, err := getOrderByColumnsByObjectType(objectType)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  
   239  	var objectBundleIDs BundleReferencesCollection
   240  	counts, err := unionLister.ListGlobal(ctx, bundleIDs, bundleIDColumn, pageSize, cursor, orderByColumns, &objectBundleIDs, conditions...)
   241  	if err != nil {
   242  		return nil, nil, err
   243  	}
   244  
   245  	bundleReferences := make([]*model.BundleReference, 0, len(objectBundleIDs))
   246  	for _, d := range objectBundleIDs {
   247  		entity, err := r.conv.FromEntity(d)
   248  		if err != nil {
   249  			return nil, nil, err
   250  		}
   251  		bundleReferences = append(bundleReferences, &entity)
   252  	}
   253  
   254  	return bundleReferences, counts, nil
   255  }
   256  
   257  func (r *repository) referenceObjectFieldName(objectType model.BundleReferenceObjectType) (string, error) {
   258  	switch objectType {
   259  	case model.BundleAPIReference:
   260  		return APIDefIDColumn, nil
   261  	case model.BundleEventReference:
   262  		return EventDefIDColumn, nil
   263  	}
   264  	return "", apperrors.NewInternalError("Invalid type of the BundleReference object")
   265  }
   266  
   267  func getDetailsByObjectType(objectType model.BundleReferenceObjectType) (string, string, []string, error) {
   268  	switch objectType {
   269  	case model.BundleAPIReference:
   270  		return APIDefTable, APIDefIDColumn, []string{APIDefIDColumn, bundleIDColumn, APIDefURLColumn}, nil
   271  	case model.BundleEventReference:
   272  		return EventDefTable, EventDefIDColumn, []string{EventDefIDColumn, bundleIDColumn}, nil
   273  	}
   274  	return "", "", []string{""}, apperrors.NewInternalError("Invalid type of the BundleReference object")
   275  }
   276  
   277  func getOrderByColumnsByObjectType(objectType model.BundleReferenceObjectType) (repo.OrderByParams, error) {
   278  	switch objectType {
   279  	case model.BundleAPIReference:
   280  		return repo.OrderByParams{repo.NewAscOrderBy(APIDefIDColumn), repo.NewAscOrderBy(bundleIDColumn), repo.NewAscOrderBy(APIDefURLColumn)}, nil
   281  	case model.BundleEventReference:
   282  		return repo.OrderByParams{repo.NewAscOrderBy(EventDefIDColumn), repo.NewAscOrderBy(bundleIDColumn)}, nil
   283  	}
   284  	return nil, apperrors.NewInternalError("Invalid type of the BundleReference object")
   285  }
   286  
   287  // IDs keeps IDs retrieved from the Compass storage.
   288  type IDs []string
   289  
   290  // Len returns the length of the IDs
   291  func (i IDs) Len() int {
   292  	return len(i)
   293  }