github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/api/resolver.go (about) 1 package api 2 3 import ( 4 "context" 5 6 "github.com/kyma-incubator/compass/components/director/pkg/resource" 7 "github.com/kyma-incubator/compass/components/director/pkg/str" 8 9 dataloader "github.com/kyma-incubator/compass/components/director/internal/dataloaders" 10 11 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 12 13 "github.com/kyma-incubator/compass/components/director/internal/model" 14 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 15 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 16 17 "github.com/kyma-incubator/compass/components/director/pkg/log" 18 "github.com/pkg/errors" 19 ) 20 21 // APIService is responsible for the service-layer APIDefinition operations. 22 // 23 //go:generate mockery --name=APIService --output=automock --outpkg=automock --case=underscore --disable-version-string 24 type APIService interface { 25 CreateInBundle(ctx context.Context, resourceType resource.Type, resourceID string, bundleID string, in model.APIDefinitionInput, spec *model.SpecInput) (string, error) 26 CreateInApplication(ctx context.Context, appID string, in model.APIDefinitionInput, spec *model.SpecInput) (string, error) 27 Update(ctx context.Context, resourceType resource.Type, id string, in model.APIDefinitionInput, spec *model.SpecInput) error 28 UpdateForApplication(ctx context.Context, id string, in model.APIDefinitionInput, specIn *model.SpecInput) error 29 Get(ctx context.Context, id string) (*model.APIDefinition, error) 30 Delete(ctx context.Context, resourceType resource.Type, id string) error 31 ListFetchRequests(ctx context.Context, specIDs []string) ([]*model.FetchRequest, error) 32 ListByApplicationID(ctx context.Context, appID string) ([]*model.APIDefinition, error) 33 ListByApplicationIDPage(ctx context.Context, appID string, pageSize int, cursor string) (*model.APIDefinitionPage, error) 34 } 35 36 // RuntimeService is responsible for the service-layer Runtime operations. 37 // 38 //go:generate mockery --name=RuntimeService --output=automock --outpkg=automock --case=underscore --disable-version-string 39 type RuntimeService interface { 40 Get(ctx context.Context, id string) (*model.Runtime, error) 41 } 42 43 // APIConverter converts EventDefinitions between the model.APIDefinition service-layer representation and the graphql-layer representation. 44 // 45 //go:generate mockery --name=APIConverter --output=automock --outpkg=automock --case=underscore --disable-version-string 46 type APIConverter interface { 47 ToGraphQL(in *model.APIDefinition, spec *model.Spec, bundleRef *model.BundleReference) (*graphql.APIDefinition, error) 48 MultipleToGraphQL(in []*model.APIDefinition, specs []*model.Spec, bundleRefs []*model.BundleReference) ([]*graphql.APIDefinition, error) 49 MultipleInputFromGraphQL(in []*graphql.APIDefinitionInput) ([]*model.APIDefinitionInput, []*model.SpecInput, error) 50 InputFromGraphQL(in *graphql.APIDefinitionInput) (*model.APIDefinitionInput, *model.SpecInput, error) 51 } 52 53 // FetchRequestConverter converts FetchRequest between the model.FetchRequest service-layer representation and the graphql-layer one. 54 // 55 //go:generate mockery --name=FetchRequestConverter --output=automock --outpkg=automock --case=underscore --disable-version-string 56 type FetchRequestConverter interface { 57 ToGraphQL(in *model.FetchRequest) (*graphql.FetchRequest, error) 58 InputFromGraphQL(in *graphql.FetchRequestInput) (*model.FetchRequestInput, error) 59 } 60 61 // BundleService is responsible for the service-layer Bundle operations. 62 // 63 //go:generate mockery --name=BundleService --output=automock --outpkg=automock --case=underscore --disable-version-string 64 type BundleService interface { 65 Get(ctx context.Context, id string) (*model.Bundle, error) 66 } 67 68 // ApplicationService is responsible for the service-layer Application operations. 69 // 70 //go:generate mockery --name=ApplicationService --output=automock --outpkg=automock --case=underscore --disable-version-string 71 type ApplicationService interface { 72 UpdateBaseURL(ctx context.Context, appID, targetURL string) error 73 } 74 75 // Resolver is an object responsible for resolver-layer APIDefinition operations 76 type Resolver struct { 77 transact persistence.Transactioner 78 svc APIService 79 bndlSvc BundleService 80 bndlRefSvc BundleReferenceService 81 rtmSvc RuntimeService 82 converter APIConverter 83 frConverter FetchRequestConverter 84 specService SpecService 85 specConverter SpecConverter 86 appSvc ApplicationService 87 } 88 89 // NewResolver returns a new object responsible for resolver-layer APIDefinition operations. 90 func NewResolver(transact persistence.Transactioner, svc APIService, rtmSvc RuntimeService, bndlSvc BundleService, bndlRefSvc BundleReferenceService, converter APIConverter, frConverter FetchRequestConverter, specService SpecService, specConverter SpecConverter, appSvc ApplicationService) *Resolver { 91 return &Resolver{ 92 transact: transact, 93 svc: svc, 94 rtmSvc: rtmSvc, 95 bndlSvc: bndlSvc, 96 bndlRefSvc: bndlRefSvc, 97 converter: converter, 98 frConverter: frConverter, 99 specService: specService, 100 specConverter: specConverter, 101 appSvc: appSvc, 102 } 103 } 104 105 // APIDefinitionsForApplication lists all APIDefinitions for a given application ID with paging. 106 func (r *Resolver) APIDefinitionsForApplication(ctx context.Context, appID string, first *int, after *graphql.PageCursor) (*graphql.APIDefinitionPage, error) { 107 tx, err := r.transact.Begin() 108 if err != nil { 109 return nil, err 110 } 111 defer r.transact.RollbackUnlessCommitted(ctx, tx) 112 113 log.C(ctx).Infof("Listing APIDefinitions for Application with ID %s", appID) 114 115 ctx = persistence.SaveToContext(ctx, tx) 116 117 var cursor string 118 if after != nil { 119 cursor = string(*after) 120 } 121 if first == nil { 122 return nil, apperrors.NewInvalidDataError("missing required parameter 'first'") 123 } 124 125 apisPage, err := r.svc.ListByApplicationIDPage(ctx, appID, *first, cursor) 126 if err != nil { 127 return nil, errors.Wrapf(err, "while listing APIDefinitions for Application with ID %s", appID) 128 } 129 130 gqlAPIs := make([]*graphql.APIDefinition, 0, len(apisPage.Data)) 131 for _, api := range apisPage.Data { 132 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 133 if err != nil { 134 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 135 } 136 137 gqlAPI, err := r.converter.ToGraphQL(api, spec, nil) 138 if err != nil { 139 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 140 } 141 142 gqlAPIs = append(gqlAPIs, gqlAPI) 143 } 144 145 if err = tx.Commit(); err != nil { 146 return nil, err 147 } 148 149 return &graphql.APIDefinitionPage{ 150 Data: gqlAPIs, 151 TotalCount: apisPage.TotalCount, 152 PageInfo: &graphql.PageInfo{ 153 StartCursor: graphql.PageCursor(apisPage.PageInfo.StartCursor), 154 EndCursor: graphql.PageCursor(apisPage.PageInfo.EndCursor), 155 HasNextPage: apisPage.PageInfo.HasNextPage, 156 }, 157 }, nil 158 } 159 160 // AddAPIDefinitionToBundle adds an APIDefinition to a Bundle with a given ID, 161 func (r *Resolver) AddAPIDefinitionToBundle(ctx context.Context, bundleID string, in graphql.APIDefinitionInput) (*graphql.APIDefinition, error) { 162 tx, err := r.transact.Begin() 163 if err != nil { 164 return nil, err 165 } 166 defer r.transact.RollbackUnlessCommitted(ctx, tx) 167 168 log.C(ctx).Infof("Adding APIDefinition to bundle with id %s", bundleID) 169 170 ctx = persistence.SaveToContext(ctx, tx) 171 172 convertedIn, convertedSpec, err := r.converter.InputFromGraphQL(&in) 173 if err != nil { 174 return nil, errors.Wrap(err, "while converting GraphQL input to APIDefinition") 175 } 176 177 bndl, err := r.bndlSvc.Get(ctx, bundleID) 178 if err != nil { 179 if apperrors.IsNotFoundError(err) { 180 return nil, apperrors.NewInvalidDataError("cannot add API to not existing bundle") 181 } 182 return nil, errors.Wrapf(err, "while getting Bundle with id %s when adding APIDefinition", bundleID) 183 } 184 185 id, err := r.svc.CreateInBundle(ctx, resource.Application, str.PtrStrToStr(bndl.ApplicationID), bundleID, *convertedIn, convertedSpec) 186 if err != nil { 187 return nil, errors.Wrapf(err, "Error occurred while creating APIDefinition in Bundle with id %s", bundleID) 188 } 189 190 api, err := r.svc.Get(ctx, id) 191 if err != nil { 192 return nil, err 193 } 194 195 if err = r.appSvc.UpdateBaseURL(ctx, str.PtrStrToStr(api.ApplicationID), in.TargetURL); err != nil { 196 return nil, errors.Wrapf(err, "while trying to update baseURL") 197 } 198 199 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 200 if err != nil { 201 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 202 } 203 204 bndlRef, err := r.bndlRefSvc.GetForBundle(ctx, model.BundleAPIReference, &api.ID, &bundleID) 205 if err != nil { 206 return nil, errors.Wrapf(err, "while getting bundle reference for APIDefinition with id %q", api.ID) 207 } 208 209 gqlAPI, err := r.converter.ToGraphQL(api, spec, bndlRef) 210 if err != nil { 211 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 212 } 213 214 err = tx.Commit() 215 if err != nil { 216 return nil, err 217 } 218 219 log.C(ctx).Infof("APIDefinition with id %s successfully added to Bundle with id %s", id, bundleID) 220 return gqlAPI, nil 221 } 222 223 // AddAPIDefinitionToApplication adds an APIDefinition in the context of an Application without Bundle 224 func (r *Resolver) AddAPIDefinitionToApplication(ctx context.Context, appID string, in graphql.APIDefinitionInput) (*graphql.APIDefinition, error) { 225 tx, err := r.transact.Begin() 226 if err != nil { 227 return nil, err 228 } 229 defer r.transact.RollbackUnlessCommitted(ctx, tx) 230 231 log.C(ctx).Infof("Adding APIDefinition to application with id %s", appID) 232 233 ctx = persistence.SaveToContext(ctx, tx) 234 235 convertedIn, convertedSpec, err := r.converter.InputFromGraphQL(&in) 236 if err != nil { 237 return nil, errors.Wrap(err, "while converting GraphQL input to APIDefinition") 238 } 239 240 id, err := r.svc.CreateInApplication(ctx, appID, *convertedIn, convertedSpec) 241 if err != nil { 242 return nil, errors.Wrapf(err, "Error occurred while creating APIDefinition in Application with id %s", appID) 243 } 244 245 api, err := r.svc.Get(ctx, id) 246 if err != nil { 247 return nil, err 248 } 249 250 if err = r.appSvc.UpdateBaseURL(ctx, str.PtrStrToStr(api.ApplicationID), in.TargetURL); err != nil { 251 return nil, errors.Wrapf(err, "while trying to update baseURL") 252 } 253 254 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 255 if err != nil { 256 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 257 } 258 259 gqlAPI, err := r.converter.ToGraphQL(api, spec, nil) 260 if err != nil { 261 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 262 } 263 264 if err = tx.Commit(); err != nil { 265 return nil, err 266 } 267 268 log.C(ctx).Infof("APIDefinition with id %s successfully added to Application with id %s", id, appID) 269 return gqlAPI, nil 270 } 271 272 // UpdateAPIDefinition updates an APIDefinition by its ID. 273 func (r *Resolver) UpdateAPIDefinition(ctx context.Context, id string, in graphql.APIDefinitionInput) (*graphql.APIDefinition, error) { 274 tx, err := r.transact.Begin() 275 if err != nil { 276 return nil, err 277 } 278 defer r.transact.RollbackUnlessCommitted(ctx, tx) 279 280 log.C(ctx).Infof("Updating APIDefinition with id %s", id) 281 282 ctx = persistence.SaveToContext(ctx, tx) 283 284 convertedIn, convertedSpec, err := r.converter.InputFromGraphQL(&in) 285 if err != nil { 286 return nil, errors.Wrapf(err, "while converting GraphQL input to APIDefinition with id %s", id) 287 } 288 289 err = r.svc.Update(ctx, resource.Application, id, *convertedIn, convertedSpec) 290 if err != nil { 291 return nil, err 292 } 293 294 api, err := r.svc.Get(ctx, id) 295 if err != nil { 296 return nil, err 297 } 298 299 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 300 if err != nil { 301 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 302 } 303 304 bndlRef, err := r.bndlRefSvc.GetForBundle(ctx, model.BundleAPIReference, &api.ID, nil) 305 if err != nil { 306 return nil, errors.Wrapf(err, "while getting bundle reference for APIDefinition with id %q", api.ID) 307 } 308 309 gqlAPI, err := r.converter.ToGraphQL(api, spec, bndlRef) 310 if err != nil { 311 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 312 } 313 314 err = tx.Commit() 315 if err != nil { 316 return nil, err 317 } 318 319 log.C(ctx).Infof("APIDefinition with id %s successfully updated.", id) 320 return gqlAPI, nil 321 } 322 323 // UpdateAPIDefinitionForApplication updates an APIDefinition for Application without being in a Bundle 324 func (r *Resolver) UpdateAPIDefinitionForApplication(ctx context.Context, id string, in graphql.APIDefinitionInput) (*graphql.APIDefinition, error) { 325 tx, err := r.transact.Begin() 326 if err != nil { 327 return nil, err 328 } 329 defer r.transact.RollbackUnlessCommitted(ctx, tx) 330 331 log.C(ctx).Infof("Updating APIDefinition with id %s", id) 332 333 ctx = persistence.SaveToContext(ctx, tx) 334 335 convertedIn, convertedSpec, err := r.converter.InputFromGraphQL(&in) 336 if err != nil { 337 return nil, errors.Wrapf(err, "while converting GraphQL input to APIDefinition with id %s", id) 338 } 339 340 if err = r.svc.UpdateForApplication(ctx, id, *convertedIn, convertedSpec); err != nil { 341 return nil, err 342 } 343 344 api, err := r.svc.Get(ctx, id) 345 if err != nil { 346 return nil, err 347 } 348 349 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 350 if err != nil { 351 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 352 } 353 354 gqlAPI, err := r.converter.ToGraphQL(api, spec, nil) 355 if err != nil { 356 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 357 } 358 359 if err = tx.Commit(); err != nil { 360 return nil, err 361 } 362 363 log.C(ctx).Infof("APIDefinition with id %s successfully updated.", id) 364 return gqlAPI, nil 365 } 366 367 // DeleteAPIDefinition deletes an APIDefinition by its ID. 368 func (r *Resolver) DeleteAPIDefinition(ctx context.Context, id string) (*graphql.APIDefinition, error) { 369 tx, err := r.transact.Begin() 370 if err != nil { 371 return nil, err 372 } 373 defer r.transact.RollbackUnlessCommitted(ctx, tx) 374 375 log.C(ctx).Infof("Deleting APIDefinition with id %s", id) 376 377 ctx = persistence.SaveToContext(ctx, tx) 378 379 api, err := r.svc.Get(ctx, id) 380 if err != nil { 381 return nil, err 382 } 383 384 spec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, api.ID) 385 if err != nil { 386 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", api.ID) 387 } 388 389 bndlRef, err := r.bndlRefSvc.GetForBundle(ctx, model.BundleAPIReference, &api.ID, nil) 390 if err != nil { 391 return nil, errors.Wrapf(err, "while getting bundle reference for APIDefinition with id %q", api.ID) 392 } 393 394 gqlAPI, err := r.converter.ToGraphQL(api, spec, bndlRef) 395 if err != nil { 396 return nil, errors.Wrapf(err, "while converting APIDefinition with id %q to graphQL", api.ID) 397 } 398 399 err = r.svc.Delete(ctx, resource.Application, id) 400 if err != nil { 401 return nil, err 402 } 403 404 err = tx.Commit() 405 if err != nil { 406 return nil, err 407 } 408 409 log.C(ctx).Infof("APIDefinition with id %s successfully deleted.", id) 410 return gqlAPI, nil 411 } 412 413 // RefetchAPISpec refetches an APISpec for APIDefinition with given ID. 414 func (r *Resolver) RefetchAPISpec(ctx context.Context, apiID string) (*graphql.APISpec, error) { 415 tx, err := r.transact.Begin() 416 if err != nil { 417 return nil, err 418 } 419 defer r.transact.RollbackUnlessCommitted(ctx, tx) 420 421 log.C(ctx).Infof("Refetching APISpec for API with id %s", apiID) 422 423 ctx = persistence.SaveToContext(ctx, tx) 424 425 dbSpec, err := r.specService.GetByReferenceObjectID(ctx, resource.Application, model.APISpecReference, apiID) 426 if err != nil { 427 return nil, errors.Wrapf(err, "while getting spec for APIDefinition with id %q", apiID) 428 } 429 430 if dbSpec == nil { 431 return nil, errors.Errorf("spec for API with id %q not found", apiID) 432 } 433 434 spec, err := r.specService.RefetchSpec(ctx, dbSpec.ID, model.APISpecReference) 435 if err != nil { 436 return nil, err 437 } 438 439 converted, err := r.specConverter.ToGraphQLAPISpec(spec) 440 if err != nil { 441 return nil, err 442 } 443 444 err = tx.Commit() 445 if err != nil { 446 return nil, err 447 } 448 449 log.C(ctx).Infof("Successfully refetched APISpec for APIDefinition with id %s", apiID) 450 return converted, nil 451 } 452 453 // FetchRequest returns a FetchRequest by a given EventSpec via dataloaders. 454 func (r *Resolver) FetchRequest(ctx context.Context, obj *graphql.APISpec) (*graphql.FetchRequest, error) { 455 params := dataloader.ParamFetchRequestAPIDef{ID: obj.ID, Ctx: ctx} 456 return dataloader.ForFetchRequestAPIDef(ctx).FetchRequestAPIDefByID.Load(params) 457 } 458 459 // FetchRequestAPIDefDataLoader is the dataloader implementation. 460 func (r *Resolver) FetchRequestAPIDefDataLoader(keys []dataloader.ParamFetchRequestAPIDef) ([]*graphql.FetchRequest, []error) { 461 if len(keys) == 0 { 462 return nil, []error{apperrors.NewInternalError("No APIDef specs found")} 463 } 464 465 ctx := keys[0].Ctx 466 467 specIDs := make([]string, 0, len(keys)) 468 for _, key := range keys { 469 if key.ID == "" { 470 return nil, []error{apperrors.NewInternalError("Cannot fetch FetchRequest. APIDefinition Spec ID is empty")} 471 } 472 specIDs = append(specIDs, key.ID) 473 } 474 475 tx, err := r.transact.Begin() 476 if err != nil { 477 return nil, []error{err} 478 } 479 defer r.transact.RollbackUnlessCommitted(ctx, tx) 480 481 ctx = persistence.SaveToContext(ctx, tx) 482 483 fetchRequests, err := r.svc.ListFetchRequests(ctx, specIDs) 484 if err != nil { 485 return nil, []error{err} 486 } 487 488 if fetchRequests == nil { 489 return nil, nil 490 } 491 492 gqlFetchRequests := make([]*graphql.FetchRequest, 0, len(fetchRequests)) 493 for _, fr := range fetchRequests { 494 fetchRequest, err := r.frConverter.ToGraphQL(fr) 495 if err != nil { 496 return nil, []error{err} 497 } 498 gqlFetchRequests = append(gqlFetchRequests, fetchRequest) 499 } 500 501 if err = tx.Commit(); err != nil { 502 return nil, []error{err} 503 } 504 505 log.C(ctx).Infof("Successfully fetched requests for Specifications %v", specIDs) 506 return gqlFetchRequests, nil 507 }