...

Source file src/edge-infra.dev/pkg/edge/api/services/bsp_site_service.go

Documentation: edge-infra.dev/pkg/edge/api/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"time"
    10  
    11  	"edge-infra.dev/pkg/edge/api/bsl/types"
    12  	"edge-infra.dev/pkg/edge/api/graph/mapper"
    13  	"edge-infra.dev/pkg/edge/api/graph/model"
    14  	"edge-infra.dev/pkg/edge/api/middleware"
    15  	sqlquery "edge-infra.dev/pkg/edge/api/sql"
    16  	"edge-infra.dev/pkg/edge/api/utils"
    17  	"edge-infra.dev/pkg/edge/bsl"
    18  	"edge-infra.dev/pkg/edge/constants/api/banner"
    19  )
    20  
    21  const (
    22  	// BslSitePath the request path to create a bsl site.
    23  	BslSitePath = "/site/v1/sites"
    24  	// BslSitePath the request path to delete a bsl site.
    25  	BslUpdateSitePath = "/site/v1/sites/%s"
    26  	// BslOrgPath the request path to get a bsl org.
    27  	BslOrgPath = "/provisioning/organizations"
    28  	// ActiveStatus active status for bsl site
    29  	ActiveStatus = "ACTIVE"
    30  	// InActiveStatus inactive status for bsl site
    31  	InActiveStatus = "INACTIVE"
    32  	// DefaultLatitude is the default latitude value used when a latitude is not provided.
    33  	DefaultLatitude = 90.0
    34  	// DefaultLongitude is the default longitude value used when a longitude is not provided.
    35  	DefaultLongitude = 180.0
    36  
    37  	deletedSuffix = "-deleted-"
    38  )
    39  
    40  //go:generate mockgen -destination=../mocks/mock_bsp_site_service.go -package=mocks edge-infra.dev/pkg/edge/api/services BSLSiteService
    41  type BSLSiteService interface {
    42  	CreateBSLSite(ctx context.Context, storeName, enterpriseUnitName, status string, latitude, longitude float64, clusterBanner *model.Banner, referenceID string) (*bsl.BSLInfo, error)
    43  	GetOrCreateSite(ctx context.Context, storeInfo *model.StoreInfo, clusterBanner *model.Banner, storeName, clusterGUID string) (*bsl.BSLInfo, bool, error)
    44  	UpdateBSLSiteByID(ctx context.Context, clusterBanner *model.Banner, siteID string, site model.Site) (*model.StoreSiteInfo, error)
    45  	GetBSLSiteByID(ctx context.Context, siteID string, clusterBanner *model.Banner) (*bsl.BSLInfo, error)
    46  	DeleteBSLSite(ctx context.Context, bslSite *bsl.BSLInfo, siteBanner *model.Banner) error
    47  	DoesSiteHaveAttachedCluster(ctx context.Context, siteID string) (bool, error)
    48  }
    49  
    50  type bslSiteService struct {
    51  	BSLClient *bsl.Client
    52  	SQLDB     *sql.DB
    53  }
    54  
    55  func (s *bslSiteService) DoesSiteHaveAttachedCluster(ctx context.Context, siteID string) (bool, error) {
    56  	var exists bool
    57  	row := s.SQLDB.QueryRowContext(ctx, sqlquery.DoesClusterWithSiteIDExist, siteID)
    58  	if err := row.Scan(&exists); err != nil {
    59  		return false, err
    60  	}
    61  	return exists, nil
    62  }
    63  
    64  func (s *bslSiteService) GetBSLSiteByID(ctx context.Context, siteID string, clusterBanner *model.Banner) (*bsl.BSLInfo, error) {
    65  	user := middleware.ForContext(ctx)
    66  	organization := bsl.GetOrgShortName(user.Organization)
    67  	client, err := s.BSLClient.WithBackendOrgAccessKey(ctx, organization)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	if banner.Type(clusterBanner.BannerType) == banner.Org {
    72  		client.SetOrgID(clusterBanner.BannerBSLId)
    73  	}
    74  	storeSite := bsl.NewBSLInfo()
    75  	err = getBSLSite(client, siteID, storeSite)
    76  	return storeSite, err
    77  }
    78  
    79  func (s *bslSiteService) UpdateBSLSiteByID(ctx context.Context, clusterBanner *model.Banner, siteID string, site model.Site) (*model.StoreSiteInfo, error) {
    80  	user := middleware.ForContext(ctx)
    81  	organization := bsl.GetOrgShortName(user.Organization)
    82  	client, errs := s.BSLClient.WithBackendOrgAccessKey(ctx, organization)
    83  	if errs != nil {
    84  		return nil, errs
    85  	}
    86  	if banner.Type(clusterBanner.BannerType) == banner.Org {
    87  		client.SetOrgID(clusterBanner.BannerBSLId)
    88  	}
    89  	siteResponse := types.NewBslSite()
    90  	err := getBSLSite(client, siteID, siteResponse)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	siteContacts := make(map[string]*types.Contact, 0)
    96  	for _, v := range site.Contacts {
    97  		if v != nil {
    98  			c := &types.Contact{}
    99  			if v.Contact.ContactPerson != nil {
   100  				c.ContactPerson = v.Contact.ContactPerson
   101  			}
   102  			if v.Contact.Email != nil {
   103  				c.Email = v.Contact.Email
   104  			}
   105  			if v.Contact.PhoneNumber != nil {
   106  				c.PhoneNumber = v.Contact.PhoneNumber
   107  			}
   108  			if v.Contact.PhoneNumberCountryCode != nil {
   109  				c.PhoneNumberCountryCode = v.Contact.PhoneNumberCountryCode
   110  			}
   111  			siteContacts[v.Identifier] = c
   112  		}
   113  	}
   114  	siteResponse.Contacts = siteContacts
   115  	return updateBSLSite(client, siteID, mapper.ToBSLSite(&site, siteResponse))
   116  }
   117  
   118  // GetOrCreateSite check if site exists and create one if not
   119  func (s *bslSiteService) GetOrCreateSite(ctx context.Context, storeInfo *model.StoreInfo, clusterBanner *model.Banner, storeName, clusterGUID string) (*bsl.BSLInfo, bool, error) {
   120  	if storeInfo == nil {
   121  		return &bsl.BSLInfo{}, false, nil
   122  	}
   123  	// check and get existed site if siteID is provided
   124  	if !utils.IsNullOrEmpty(storeInfo.SiteID) {
   125  		bslSite, err := s.checkIfSiteExists(ctx, *storeInfo.SiteID, clusterBanner)
   126  		return bslSite, false, err
   127  	}
   128  	//create site if siteID is not provided
   129  	createSite := storeInfo.CreateSite != nil && *storeInfo.CreateSite
   130  	if !createSite {
   131  		return &bsl.BSLInfo{}, false, nil
   132  	}
   133  	bslSite, err := s.registerSite(ctx, storeInfo, storeName, clusterGUID, clusterBanner)
   134  	if err != nil {
   135  		return bslSite, false, err
   136  	}
   137  	return bslSite, true, nil
   138  }
   139  
   140  func (s *bslSiteService) checkIfSiteExists(ctx context.Context, siteID string, clusterBanner *model.Banner) (*bsl.BSLInfo, error) {
   141  	//check if the provided site id has been used by a non-deleted cluster
   142  	exists, err := s.DoesSiteHaveAttachedCluster(ctx, siteID)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	if exists {
   147  		return nil, errors.New("bsl site id has already been registered with a cluster")
   148  	}
   149  	//get existed site
   150  	return s.GetSiteIfItExists(ctx, siteID, clusterBanner)
   151  }
   152  
   153  func (s *bslSiteService) registerSite(ctx context.Context, storeInfo *model.StoreInfo, storeName, clusterEdgeID string, clusterBanner *model.Banner) (*bsl.BSLInfo, error) {
   154  	latitude := VerifyCoordinate(storeInfo.Latitude, DefaultLatitude)
   155  	longitude := VerifyCoordinate(storeInfo.Longitude, DefaultLongitude)
   156  	referenceID := ""
   157  	if !utils.IsNullOrEmpty(storeInfo.ReferenceID) {
   158  		referenceID = *storeInfo.ReferenceID
   159  	}
   160  	return s.CreateBSLSite(ctx, storeName, clusterEdgeID, ActiveStatus, latitude, longitude, clusterBanner, referenceID)
   161  }
   162  
   163  func (s *bslSiteService) DeleteBSLSite(ctx context.Context, bslSite *bsl.BSLInfo, siteBanner *model.Banner) error {
   164  	user := middleware.ForContext(ctx)
   165  	organization := bsl.GetOrgShortName(user.Organization)
   166  	client, err := s.BSLClient.WithBackendOrgAccessKey(ctx, organization)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	if banner.Type(siteBanner.BannerType) == banner.Org {
   171  		client.SetOrgID(siteBanner.BannerBSLId)
   172  	}
   173  	suffix := deletedSuffix + time.Now().UTC().Format(mapper.TimeFormat)
   174  	sitePayload := &types.BSLSite{
   175  		SiteName:           bslSite.SiteName + suffix,
   176  		EnterpriseUnitName: bslSite.EnterpriseUnitName,
   177  		Status:             InActiveStatus,
   178  		Coordinates: types.Coordinates{
   179  			Latitude:  bslSite.Coordinates.Latitude,
   180  			Longitude: bslSite.Coordinates.Longitude,
   181  		},
   182  	}
   183  	return client.SetPayload(sitePayload).Put(fmt.Sprintf(BslUpdateSitePath, bslSite.ID))
   184  }
   185  
   186  func (s *bslSiteService) CreateBSLSite(ctx context.Context, storeName, enterpriseUnitName, status string, latitude, longitude float64, clusterBanner *model.Banner, referenceID string) (*bsl.BSLInfo, error) {
   187  	client := s.BSLClient.WithUserTokenCredentials(ctx)
   188  	payload := &types.BSLSite{
   189  		SiteName:           storeName,
   190  		EnterpriseUnitName: enterpriseUnitName,
   191  		Status:             status,
   192  		Coordinates: types.Coordinates{
   193  			Latitude:  latitude,
   194  			Longitude: longitude,
   195  		},
   196  		ReferenceID: referenceID,
   197  	}
   198  	return createBSLSite(client, payload, clusterBanner)
   199  }
   200  
   201  func (s *bslSiteService) GetSiteIfItExists(ctx context.Context, euid string, clusterBanner *model.Banner) (*bsl.BSLInfo, error) {
   202  	client := s.BSLClient.WithUserTokenCredentials(ctx)
   203  	if banner.Type(clusterBanner.BannerType) == banner.Org {
   204  		client.SetOrgID(clusterBanner.BannerBSLId)
   205  	}
   206  	site := &bsl.BSLInfo{}
   207  	err := getBSLSite(client, euid, site)
   208  	return site, err
   209  }
   210  
   211  // createBSLSite - creates a new BSL Site and returns the enterprise unit id of the site.
   212  func createBSLSite(client *bsl.Request, payload *types.BSLSite, clusterBanner *model.Banner) (*bsl.BSLInfo, error) {
   213  	bslSite := &bsl.BSLInfo{}
   214  	if banner.Type(clusterBanner.BannerType) == banner.Org {
   215  		client.SetOrgID(clusterBanner.BannerBSLId)
   216  	}
   217  	err := client.SetPayload(payload).JSON(http.MethodPost, BslSitePath, bslSite)
   218  	if err != nil {
   219  		return bslSite, err
   220  	}
   221  	return bslSite, nil
   222  }
   223  
   224  // getBSLSite checks if the BSL Site with the provided euid exists and returns it.
   225  func getBSLSite(client *bsl.Request, bslID string, siteResponse interface{}) error {
   226  	return client.JSON(http.MethodGet, fmt.Sprintf("%s/%s", BslSitePath, bslID), siteResponse)
   227  }
   228  
   229  type UpdateSiteResponse struct {
   230  	model.StoreSiteInfo
   231  	Contacts map[string]model.Contact `json:"contacts"`
   232  }
   233  
   234  // updateBSLSite updates a bsl site with the specified euid.
   235  func updateBSLSite(client *bsl.Request, bslID string, site *types.BslSite) (*model.StoreSiteInfo, error) {
   236  	response := &UpdateSiteResponse{}
   237  	err := client.SetPayload(site).JSON(http.MethodPut, fmt.Sprintf("%s/%s", BslSitePath, bslID), response)
   238  	return response.toStoreSiteInfo(), err
   239  }
   240  
   241  func (u *UpdateSiteResponse) toStoreSiteInfo() *model.StoreSiteInfo {
   242  	for k, v := range u.Contacts {
   243  		u.StoreSiteInfo.Contacts = append(u.StoreSiteInfo.Contacts, &model.Contacts{
   244  			Identifier: k,
   245  			Details: &model.Contact{
   246  				Email:                  v.Email,
   247  				ContactPerson:          v.ContactPerson,
   248  				PhoneNumber:            v.PhoneNumber,
   249  				PhoneNumberCountryCode: v.PhoneNumberCountryCode,
   250  			},
   251  		})
   252  	}
   253  	return &u.StoreSiteInfo
   254  }
   255  
   256  // VerifyCoordinate checks the latitude or longitude value to ensure it is not nil,
   257  //
   258  //	if nil it sets a default value.
   259  //
   260  // if the value is not nil it returns the initial value.
   261  func VerifyCoordinate(coordinate *float64, defaultCoordinate float64) float64 {
   262  	if coordinate == nil {
   263  		return defaultCoordinate
   264  	}
   265  	return *coordinate
   266  }
   267  
   268  // NewBSLSiteService creates a new BSL Site Service.
   269  func NewBSLSiteService(client *bsl.Client, sqlDB *sql.DB) BSLSiteService { //nolint
   270  	return &bslSiteService{
   271  		BSLClient: client,
   272  		SQLDB:     sqlDB,
   273  	}
   274  }
   275  

View as plain text