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
23 BslSitePath = "/site/v1/sites"
24
25 BslUpdateSitePath = "/site/v1/sites/%s"
26
27 BslOrgPath = "/provisioning/organizations"
28
29 ActiveStatus = "ACTIVE"
30
31 InActiveStatus = "INACTIVE"
32
33 DefaultLatitude = 90.0
34
35 DefaultLongitude = 180.0
36
37 deletedSuffix = "-deleted-"
38 )
39
40
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
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
124 if !utils.IsNullOrEmpty(storeInfo.SiteID) {
125 bslSite, err := s.checkIfSiteExists(ctx, *storeInfo.SiteID, clusterBanner)
126 return bslSite, false, err
127 }
128
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
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
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
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
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
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
257
258
259
260
261 func VerifyCoordinate(coordinate *float64, defaultCoordinate float64) float64 {
262 if coordinate == nil {
263 return defaultCoordinate
264 }
265 return *coordinate
266 }
267
268
269 func NewBSLSiteService(client *bsl.Client, sqlDB *sql.DB) BSLSiteService {
270 return &bslSiteService{
271 BSLClient: client,
272 SQLDB: sqlDB,
273 }
274 }
275
View as plain text