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 }