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  }