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