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

     1  package eventdef
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/kyma-incubator/compass/components/director/internal/domain/bundlereferences"
     7  	"github.com/kyma-incubator/compass/components/director/pkg/pagination"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    12  
    13  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/internal/model"
    16  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  const eventAPIDefTable string = `"public"."event_api_definitions"`
    21  
    22  var (
    23  	idColumn                 = "id"
    24  	appColumn                = "app_id"
    25  	appTemplateVersionColumn = "app_template_version_id"
    26  	bundleColumn             = "bundle_id"
    27  	eventDefColumns          = []string{idColumn, appColumn, "app_template_version_id", "package_id", "name", "description", "group_name", "ord_id", "local_tenant_id",
    28  		"short_description", "system_instance_aware", "policy_level", "custom_policy_level", "changelog_entries", "links", "tags", "countries", "release_status",
    29  		"sunset_date", "labels", "visibility", "disabled", "part_of_products", "line_of_business", "industry", "version_value", "version_deprecated", "version_deprecated_since",
    30  		"version_for_removal", "ready", "created_at", "updated_at", "deleted_at", "error", "extensible", "successors", "resource_hash", "hierarchy", "documentation_labels"}
    31  	idColumns        = []string{idColumn}
    32  	updatableColumns = []string{"package_id", "name", "description", "group_name", "ord_id", "local_tenant_id",
    33  		"short_description", "system_instance_aware", "policy_level", "custom_policy_level", "changelog_entries", "links", "tags", "countries", "release_status",
    34  		"sunset_date", "labels", "visibility", "disabled", "part_of_products", "line_of_business", "industry", "version_value", "version_deprecated", "version_deprecated_since",
    35  		"version_for_removal", "ready", "created_at", "updated_at", "deleted_at", "error", "extensible", "successors", "resource_hash", "hierarchy", "documentation_labels"}
    36  )
    37  
    38  // EventAPIDefinitionConverter converts EventDefinitions between the model.EventDefinition service-layer representation and the repo-layer representation Entity.
    39  //
    40  //go:generate mockery --name=EventAPIDefinitionConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    41  type EventAPIDefinitionConverter interface {
    42  	FromEntity(entity *Entity) *model.EventDefinition
    43  	ToEntity(apiModel *model.EventDefinition) *Entity
    44  }
    45  
    46  type pgRepository struct {
    47  	singleGetter          repo.SingleGetter
    48  	singleGetterGlobal    repo.SingleGetterGlobal
    49  	bundleRefQueryBuilder repo.QueryBuilderGlobal
    50  	lister                repo.Lister
    51  	listerGlobal          repo.ListerGlobal
    52  	creator               repo.Creator
    53  	creatorGlobal         repo.CreatorGlobal
    54  	updater               repo.Updater
    55  	updaterGlobal         repo.UpdaterGlobal
    56  	deleter               repo.Deleter
    57  	deleterGlobal         repo.DeleterGlobal
    58  	existQuerier          repo.ExistQuerier
    59  	pageableQuerier       repo.PageableQuerier
    60  	conv                  EventAPIDefinitionConverter
    61  }
    62  
    63  // NewRepository returns a new entity responsible for repo-layer EventDefinitions operations.
    64  func NewRepository(conv EventAPIDefinitionConverter) *pgRepository {
    65  	return &pgRepository{
    66  		singleGetter:          repo.NewSingleGetter(eventAPIDefTable, eventDefColumns),
    67  		singleGetterGlobal:    repo.NewSingleGetterGlobal(resource.EventDefinition, eventAPIDefTable, eventDefColumns),
    68  		bundleRefQueryBuilder: repo.NewQueryBuilderGlobal(resource.BundleReference, bundlereferences.BundleReferenceTable, []string{bundlereferences.EventDefIDColumn}),
    69  		lister:                repo.NewLister(eventAPIDefTable, eventDefColumns),
    70  		listerGlobal:          repo.NewListerGlobal(resource.EventDefinition, eventAPIDefTable, eventDefColumns),
    71  		creator:               repo.NewCreator(eventAPIDefTable, eventDefColumns),
    72  		creatorGlobal:         repo.NewCreatorGlobal(resource.EventDefinition, eventAPIDefTable, eventDefColumns),
    73  		updater:               repo.NewUpdater(eventAPIDefTable, updatableColumns, idColumns),
    74  		updaterGlobal:         repo.NewUpdaterGlobal(resource.EventDefinition, eventAPIDefTable, updatableColumns, idColumns),
    75  		deleter:               repo.NewDeleter(eventAPIDefTable),
    76  		deleterGlobal:         repo.NewDeleterGlobal(resource.EventDefinition, eventAPIDefTable),
    77  		existQuerier:          repo.NewExistQuerier(eventAPIDefTable),
    78  		pageableQuerier:       repo.NewPageableQuerier(eventAPIDefTable, eventDefColumns),
    79  		conv:                  conv,
    80  	}
    81  }
    82  
    83  // EventAPIDefCollection is an array of Entities
    84  type EventAPIDefCollection []Entity
    85  
    86  // Len returns the length of the collection
    87  func (r EventAPIDefCollection) Len() int {
    88  	return len(r)
    89  }
    90  
    91  // GetByID retrieves the EventDefinition with matching ID from the Compass storage.
    92  func (r *pgRepository) GetByID(ctx context.Context, tenantID string, id string) (*model.EventDefinition, error) {
    93  	var eventAPIDefEntity Entity
    94  	err := r.singleGetter.Get(ctx, resource.EventDefinition, tenantID, repo.Conditions{repo.NewEqualCondition(idColumn, id)}, repo.NoOrderBy, &eventAPIDefEntity)
    95  	if err != nil {
    96  		return nil, errors.Wrapf(err, "while getting EventDefinition with id %s", id)
    97  	}
    98  
    99  	eventAPIDefModel := r.conv.FromEntity(&eventAPIDefEntity)
   100  	return eventAPIDefModel, nil
   101  }
   102  
   103  // GetByIDGlobal retrieves the EventDefinition with matching ID from the Compass storage.
   104  func (r *pgRepository) GetByIDGlobal(ctx context.Context, id string) (*model.EventDefinition, error) {
   105  	var eventAPIDefEntity Entity
   106  	err := r.singleGetterGlobal.GetGlobal(ctx, repo.Conditions{repo.NewEqualCondition(idColumn, id)}, repo.NoOrderBy, &eventAPIDefEntity)
   107  	if err != nil {
   108  		return nil, errors.Wrapf(err, "while getting EventDefinition with id %s", id)
   109  	}
   110  
   111  	eventAPIDefModel := r.conv.FromEntity(&eventAPIDefEntity)
   112  	return eventAPIDefModel, nil
   113  }
   114  
   115  // GetForBundle gets an EventDefinition by its id.
   116  // the bundleID remains for backwards compatibility above in the layers; we are sure that the correct Event will be fetched because there can't be two records with the same ID
   117  func (r *pgRepository) GetForBundle(ctx context.Context, tenant string, id string, bundleID string) (*model.EventDefinition, error) {
   118  	return r.GetByID(ctx, tenant, id)
   119  }
   120  
   121  // ListByApplicationIDPage lists all EventDefinitions for a given application ID with paging.
   122  func (r *pgRepository) ListByApplicationIDPage(ctx context.Context, tenantID string, appID string, pageSize int, cursor string) (*model.EventDefinitionPage, error) {
   123  	var apiDefCollection EventAPIDefCollection
   124  	page, totalCount, err := r.pageableQuerier.List(ctx, resource.EventDefinition, tenantID, pageSize, cursor, idColumn, &apiDefCollection, repo.NewEqualCondition("app_id", appID))
   125  
   126  	if err != nil {
   127  		return nil, errors.Wrap(err, "while decoding page cursor")
   128  	}
   129  
   130  	items := make([]*model.EventDefinition, 0, len(apiDefCollection))
   131  	for _, api := range apiDefCollection {
   132  		m := r.conv.FromEntity(&api)
   133  		items = append(items, m)
   134  	}
   135  
   136  	return &model.EventDefinitionPage{
   137  		Data:       items,
   138  		TotalCount: totalCount,
   139  		PageInfo:   page,
   140  	}, nil
   141  }
   142  
   143  // ListByBundleIDs retrieves all EventDefinitions for a Bundle in pages. Each Bundle is extracted from the input array of bundleIDs. The input bundleReferences array is used for getting the appropriate EventDefinition IDs.
   144  func (r *pgRepository) ListByBundleIDs(ctx context.Context, tenantID string, bundleIDs []string, bundleRefs []*model.BundleReference, totalCounts map[string]int, pageSize int, cursor string) ([]*model.EventDefinitionPage, error) {
   145  	eventDefIDs := make([]string, 0, len(bundleRefs))
   146  	for _, ref := range bundleRefs {
   147  		eventDefIDs = append(eventDefIDs, *ref.ObjectID)
   148  	}
   149  
   150  	var conditions repo.Conditions
   151  	if len(eventDefIDs) > 0 {
   152  		conditions = repo.Conditions{
   153  			repo.NewInConditionForStringValues(idColumn, eventDefIDs),
   154  		}
   155  	}
   156  
   157  	var eventCollection EventAPIDefCollection
   158  	err := r.lister.List(ctx, resource.EventDefinition, tenantID, &eventCollection, conditions...)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	refsByBundleID, eventDefsByEventDefID := r.groupEntitiesByID(bundleRefs, eventCollection)
   164  
   165  	offset, err := pagination.DecodeOffsetCursor(cursor)
   166  	if err != nil {
   167  		return nil, errors.Wrap(err, "while decoding page cursor")
   168  	}
   169  
   170  	eventDefPages := make([]*model.EventDefinitionPage, 0, len(bundleIDs))
   171  	for _, bundleID := range bundleIDs {
   172  		ids := getEventDefIDsForBundle(refsByBundleID[bundleID])
   173  		eventDefs := getEventDefsForBundle(ids, eventDefsByEventDefID)
   174  		hasNextPage := false
   175  		endCursor := ""
   176  		if totalCounts[bundleID] > offset+len(eventDefs) {
   177  			hasNextPage = true
   178  			endCursor = pagination.EncodeNextOffsetCursor(offset, pageSize)
   179  		}
   180  
   181  		page := &pagination.Page{
   182  			StartCursor: cursor,
   183  			EndCursor:   endCursor,
   184  			HasNextPage: hasNextPage,
   185  		}
   186  
   187  		eventDefPages = append(eventDefPages, &model.EventDefinitionPage{Data: eventDefs, TotalCount: totalCounts[bundleID], PageInfo: page})
   188  	}
   189  
   190  	return eventDefPages, nil
   191  }
   192  
   193  // ListByResourceID lists all EventDefinitions for a given resource ID and resource.Type
   194  func (r *pgRepository) ListByResourceID(ctx context.Context, tenantID, resourceID string, resourceType resource.Type) ([]*model.EventDefinition, error) {
   195  	eventCollection := EventAPIDefCollection{}
   196  
   197  	var condition repo.Condition
   198  	var err error
   199  	if resourceType == resource.Application {
   200  		condition = repo.NewEqualCondition(appColumn, resourceID)
   201  		err = r.lister.ListWithSelectForUpdate(ctx, resource.EventDefinition, tenantID, &eventCollection, condition)
   202  	} else {
   203  		condition = repo.NewEqualCondition(appTemplateVersionColumn, resourceID)
   204  		err = r.listerGlobal.ListGlobalWithSelectForUpdate(ctx, &eventCollection, condition)
   205  	}
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	events := make([]*model.EventDefinition, 0, eventCollection.Len())
   211  	for _, event := range eventCollection {
   212  		eventModel := r.conv.FromEntity(&event)
   213  		events = append(events, eventModel)
   214  	}
   215  	return events, nil
   216  }
   217  
   218  // Create creates an EventDefinition.
   219  func (r *pgRepository) Create(ctx context.Context, tenant string, item *model.EventDefinition) error {
   220  	if item == nil {
   221  		return apperrors.NewInternalError("item cannot be nil")
   222  	}
   223  
   224  	entity := r.conv.ToEntity(item)
   225  
   226  	log.C(ctx).Debugf("Persisting Event-Definition entity with id %s to db", item.ID)
   227  	err := r.creator.Create(ctx, resource.EventDefinition, tenant, entity)
   228  	if err != nil {
   229  		return errors.Wrap(err, "while saving entity to db")
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  // CreateGlobal creates an EventDefinition without tenant isolation.
   236  func (r *pgRepository) CreateGlobal(ctx context.Context, item *model.EventDefinition) error {
   237  	if item == nil {
   238  		return apperrors.NewInternalError("item cannot be nil")
   239  	}
   240  
   241  	entity := r.conv.ToEntity(item)
   242  
   243  	log.C(ctx).Debugf("Persisting Event-Definition entity with id %s to db", item.ID)
   244  	err := r.creatorGlobal.Create(ctx, entity)
   245  	if err != nil {
   246  		return errors.Wrap(err, "while saving entity to db")
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  // CreateMany creates many EventDefinitions.
   253  func (r *pgRepository) CreateMany(ctx context.Context, tenant string, items []*model.EventDefinition) error {
   254  	for index, item := range items {
   255  		entity := r.conv.ToEntity(item)
   256  		err := r.creator.Create(ctx, resource.EventDefinition, tenant, entity)
   257  		if err != nil {
   258  			return errors.Wrapf(err, "while persisting %d item", index)
   259  		}
   260  	}
   261  
   262  	return nil
   263  }
   264  
   265  // Update updates an EventDefinition.
   266  func (r *pgRepository) Update(ctx context.Context, tenant string, item *model.EventDefinition) error {
   267  	if item == nil {
   268  		return apperrors.NewInternalError("item cannot be nil")
   269  	}
   270  
   271  	entity := r.conv.ToEntity(item)
   272  
   273  	return r.updater.UpdateSingle(ctx, resource.EventDefinition, tenant, entity)
   274  }
   275  
   276  func (r *pgRepository) UpdateGlobal(ctx context.Context, item *model.EventDefinition) error {
   277  	if item == nil {
   278  		return apperrors.NewInternalError("item cannot be nil")
   279  	}
   280  
   281  	entity := r.conv.ToEntity(item)
   282  
   283  	return r.updaterGlobal.UpdateSingleGlobal(ctx, entity)
   284  }
   285  
   286  // Exists checks if an EventDefinition with a given ID exists.
   287  func (r *pgRepository) Exists(ctx context.Context, tenantID, id string) (bool, error) {
   288  	return r.existQuerier.Exists(ctx, resource.EventDefinition, tenantID, repo.Conditions{repo.NewEqualCondition(idColumn, id)})
   289  }
   290  
   291  // Delete deletes an EventDefinition by its ID.
   292  func (r *pgRepository) Delete(ctx context.Context, tenantID string, id string) error {
   293  	return r.deleter.DeleteOne(ctx, resource.EventDefinition, tenantID, repo.Conditions{repo.NewEqualCondition(idColumn, id)})
   294  }
   295  
   296  // DeleteGlobal deletes an EventDefinition by its ID without tenant isolation.
   297  func (r *pgRepository) DeleteGlobal(ctx context.Context, id string) error {
   298  	return r.deleterGlobal.DeleteOneGlobal(ctx, repo.Conditions{repo.NewEqualCondition(idColumn, id)})
   299  }
   300  
   301  // DeleteAllByBundleID deletes all EventDefinitions for a given bundle ID.
   302  func (r *pgRepository) DeleteAllByBundleID(ctx context.Context, tenantID, bundleID string) error {
   303  	subqueryConditions := repo.Conditions{
   304  		repo.NewEqualCondition(bundleColumn, bundleID),
   305  		repo.NewNotNullCondition(bundlereferences.EventDefIDColumn),
   306  	}
   307  	subquery, args, err := r.bundleRefQueryBuilder.BuildQueryGlobal(false, subqueryConditions...)
   308  	if err != nil {
   309  		return err
   310  	}
   311  
   312  	inOperatorConditions := repo.Conditions{
   313  		repo.NewInConditionForSubQuery(idColumn, subquery, args),
   314  	}
   315  
   316  	return r.deleter.DeleteMany(ctx, resource.EventDefinition, tenantID, inOperatorConditions)
   317  }
   318  
   319  func getEventDefsForBundle(ids []string, defs map[string]*model.EventDefinition) []*model.EventDefinition {
   320  	result := make([]*model.EventDefinition, 0, len(ids))
   321  	if len(defs) > 0 {
   322  		for _, id := range ids {
   323  			result = append(result, defs[id])
   324  		}
   325  	}
   326  	return result
   327  }
   328  
   329  func getEventDefIDsForBundle(refs []*model.BundleReference) []string {
   330  	result := make([]string, 0, len(refs))
   331  	for _, ref := range refs {
   332  		result = append(result, *ref.ObjectID)
   333  	}
   334  	return result
   335  }
   336  
   337  func (r *pgRepository) groupEntitiesByID(bundleRefs []*model.BundleReference, eventCollectionCollection EventAPIDefCollection) (map[string][]*model.BundleReference, map[string]*model.EventDefinition) {
   338  	refsByBundleID := map[string][]*model.BundleReference{}
   339  	for _, ref := range bundleRefs {
   340  		refsByBundleID[*ref.BundleID] = append(refsByBundleID[*ref.BundleID], ref)
   341  	}
   342  
   343  	eventsByAPIDefID := map[string]*model.EventDefinition{}
   344  	for _, eventEnt := range eventCollectionCollection {
   345  		m := r.conv.FromEntity(&eventEnt)
   346  		eventsByAPIDefID[eventEnt.ID] = m
   347  	}
   348  
   349  	return refsByBundleID, eventsByAPIDefID
   350  }