@adobe/spacecat-shared-data-access 1.13.2 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [@adobe/spacecat-shared-data-access-v1.14.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.14.0...@adobe/spacecat-shared-data-access-v1.14.1) (2024-02-05)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** update external fixes ([#129](https://github.com/adobe/spacecat-shared/issues/129)) ([22b6c90](https://github.com/adobe/spacecat-shared/commit/22b6c90aea25783116165fa1dacb08c631c792bc))
7
+
8
+ # [@adobe/spacecat-shared-data-access-v1.14.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.13.2...@adobe/spacecat-shared-data-access-v1.14.0) (2024-02-02)
9
+
10
+
11
+ ### Features
12
+
13
+ * site candidate data model ([#126](https://github.com/adobe/spacecat-shared/issues/126)) ([c457645](https://github.com/adobe/spacecat-shared/commit/c4576455b514675e7f3ce1efa53ecc1372fe491a))
14
+
1
15
  # [@adobe/spacecat-shared-data-access-v1.13.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.13.1...@adobe/spacecat-shared-data-access-v1.13.2) (2024-01-30)
2
16
 
3
17
 
package/README.md CHANGED
@@ -18,6 +18,13 @@ npm install @adobe/spacecat-shared-data-access
18
18
  - **updatedAt** (String): Timestamp of the last update.
19
19
  - **GSI1PK** (String): Partition key for the Global Secondary Index.
20
20
 
21
+ ### SiteCandidates
22
+ - **baseURL** (String): Base URL of the site candidate.
23
+ - **status** (String): Status of the site candidate (PENDING, IGNORED, APPROVED, ERROR)
24
+ - **createdAt** (String): Timestamp of creation.
25
+ - **updatedAt** (String): Timestamp of the last update.
26
+ - **updatedBy** (String): Slack id of the last person updated the site candidate.
27
+
21
28
  ### Audits
22
29
  - **siteId** (String): Identifier of the site being audited.
23
30
  - **SK** (String): Sort key, typically a composite of audit type and timestamp.
@@ -65,6 +72,11 @@ The module provides two main DAOs:
65
72
  - `updateSite`
66
73
  - `removeSite`
67
74
 
75
+ ### Site Candidate Functions
76
+ - `upsertSiteCandidate`
77
+ - `siteCandidateExists`
78
+ - `updateSiteCandidate`
79
+
68
80
  ### Audit Functions
69
81
  - `getAuditsForSite`
70
82
  - `getAuditForSite`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "1.13.2",
3
+ "version": "1.14.1",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -31,16 +31,17 @@
31
31
  "dependencies": {
32
32
  "@adobe/spacecat-shared-dynamo": "1.2.5",
33
33
  "@adobe/spacecat-shared-utils": "1.2.0",
34
- "@aws-sdk/client-dynamodb": "3.501.0",
35
- "@aws-sdk/lib-dynamodb": "3.501.0",
34
+ "@aws-sdk/client-dynamodb": "3.506.0",
35
+ "@aws-sdk/lib-dynamodb": "3.506.0",
36
36
  "@types/joi": "17.2.3",
37
- "joi": "17.12.0",
37
+ "joi": "17.12.1",
38
38
  "uuid": "9.0.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "chai": "4.4.1",
42
42
  "chai-as-promised": "7.1.1",
43
43
  "dynamo-db-local": "7.3.0",
44
- "sinon": "17.0.1"
44
+ "sinon": "17.0.1",
45
+ "sinon-chai": "3.7.0"
45
46
  }
46
47
  }
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ /**
14
+ * Data transfer object for Site Candidate.
15
+ */
16
+ export const SiteCandidateDto = {
17
+ /**
18
+ * Converts a Site Candidate object into a DynamoDB item.
19
+ * @param {Readonly<SiteCandidate>} siteCandidate - Site Candidate object.
20
+ * @returns {{baseURL, siteId, source, status, createdAt, updatedAt, updatedBy}}
21
+ */
22
+ toDynamoItem: (siteCandidate) => ({
23
+ baseURL: siteCandidate.getBaseURL(),
24
+ siteId: siteCandidate.getSiteId(),
25
+ source: siteCandidate.getSource(),
26
+ status: siteCandidate.getStatus(),
27
+ createdAt: siteCandidate.getCreatedAt(),
28
+ updatedAt: siteCandidate.getUpdatedAt(),
29
+ updatedBy: siteCandidate.getUpdatedBy(),
30
+ }),
31
+ };
package/src/index.d.ts CHANGED
@@ -229,6 +229,54 @@ export interface Site {
229
229
  updateOrganizationId: (organizationId: string) => Site;
230
230
  }
231
231
 
232
+ /**
233
+ * Represents a site candidate.
234
+ */
235
+ export interface SiteCandidate {
236
+ /**
237
+ * Retrieves the base URL of the site candidate.
238
+ * @returns {string} The base URL.
239
+ */
240
+ getBaseURL: () => string;
241
+
242
+ /**
243
+ * Retrieves the site id of the site candidate.
244
+ * Only set after APPROVED state
245
+ * @returns {string} site id
246
+ */
247
+ getSiteId: () => string;
248
+
249
+ /**
250
+ * Retrieves the source of the site candidate.
251
+ * @returns {string} The source
252
+ */
253
+ getSource: () => string;
254
+
255
+ /**
256
+ * Retrieves the status of the site candidate.
257
+ * @returns {string} The status
258
+ */
259
+ getStatus: () => string;
260
+
261
+ /**
262
+ * Retrieves the creation timestamp of the site candidate.
263
+ * @returns {string} The creation timestamp.
264
+ */
265
+ getCreatedAt: () => string;
266
+
267
+ /**
268
+ * Retrieves the last update timestamp of the site candidate.
269
+ * @returns {string} The last update timestamp.
270
+ */
271
+ getUpdatedAt: () => string;
272
+
273
+ /**
274
+ * Retrieves the slack id of the person who last updated the site candidate.
275
+ * @returns {string} The last update timestamp.
276
+ */
277
+ getUpdatedBy: () => string;
278
+ }
279
+
232
280
  export interface Organization {
233
281
  /**
234
282
  * Retrieves the ID of the site.
@@ -353,6 +401,11 @@ export interface DataAccess {
353
401
  removeOrganization: (
354
402
  organizationId: string,
355
403
  ) => Promise<void>;
404
+
405
+ // site candidate functions
406
+ upsertSiteCandidate: (siteCandidateDate: object) => Promise<SiteCandidate>;
407
+ siteCandidateExists: (baseURL: string) => Promise<boolean>;
408
+ updateSiteCandidate: (siteCandidate: SiteCandidate) => Promise<SiteCandidate>;
356
409
  }
357
410
 
358
411
  interface DataAccessConfig {
@@ -360,6 +413,7 @@ interface DataAccessConfig {
360
413
  tableNameLatestAudits: string;
361
414
  tableNameOrganizations: string,
362
415
  tableNameSites: string;
416
+ tableNameSiteCandidates: string;
363
417
  indexNameAllSites: string;
364
418
  indexNameAllSitesOrganizations: string,
365
419
  indexNameAllSitesByDeliveryType: string;
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ import { createDataAccess } from './service/index.js';
15
15
  const TABLE_NAME_AUDITS = 'spacecat-services-audits-dev';
16
16
  const TABLE_NAME_LATEST_AUDITS = 'spacecat-services-latest-audits-dev';
17
17
  const TABLE_NAME_SITES = 'spacecat-services-sites-dev';
18
+ const TABLE_NAME_SITE_CANDIDATES = 'spacecat-services-site-candidates-dev';
18
19
  const TABLE_NAME_ORGANIZATIONS = 'spacecat-services-organizations-dev';
19
20
 
20
21
  const INDEX_NAME_ALL_SITES = 'spacecat-services-all-sites-dev';
@@ -36,6 +37,7 @@ export default function dataAccessWrapper(fn) {
36
37
  DYNAMO_TABLE_NAME_AUDITS = TABLE_NAME_AUDITS,
37
38
  DYNAMO_TABLE_NAME_LATEST_AUDITS = TABLE_NAME_LATEST_AUDITS,
38
39
  DYNAMO_TABLE_NAME_SITES = TABLE_NAME_SITES,
40
+ DYNAMO_TABLE_NAME_SITE_CANDIDATES = TABLE_NAME_SITE_CANDIDATES,
39
41
  DYNAMO_TABLE_NAME_ORGANIZATIONS = TABLE_NAME_ORGANIZATIONS,
40
42
  DYNAMO_INDEX_NAME_ALL_SITES = INDEX_NAME_ALL_SITES,
41
43
  DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE = INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
@@ -49,6 +51,7 @@ export default function dataAccessWrapper(fn) {
49
51
  tableNameLatestAudits: DYNAMO_TABLE_NAME_LATEST_AUDITS,
50
52
  tableNameOrganizations: DYNAMO_TABLE_NAME_ORGANIZATIONS,
51
53
  tableNameSites: DYNAMO_TABLE_NAME_SITES,
54
+ tableNameSiteCandidates: DYNAMO_TABLE_NAME_SITE_CANDIDATES,
52
55
  indexNameAllSites: DYNAMO_INDEX_NAME_ALL_SITES,
53
56
  indexNameAllOrganizations: DYNAMO_INDEX_NAME_ALL_ORGANIZATIONS,
54
57
  indexNameAllSitesByDeliveryType: DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
@@ -0,0 +1,96 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { hasText, isValidUrl } from '@adobe/spacecat-shared-utils';
14
+
15
+ import { Base } from './base.js';
16
+
17
+ export const DEFAULT_UPDATED_BY = 'spacecat';
18
+
19
+ export const SITE_CANDIDATE_SOURCES = {
20
+ SPACECAT_SLACK_BOT: 'SPACECAT_SLACK_BOT',
21
+ RUM: 'RUM',
22
+ CDN: 'CDN',
23
+ };
24
+
25
+ export const SITE_CANDIDATE_STATUS = {
26
+ PENDING: 'PENDING', // site candidate notification sent and waiting for human input
27
+ IGNORED: 'IGNORED', // site candidate discarded: not to be added to star catalogue
28
+ APPROVED: 'APPROVED', // site candidate is added to star catalogue
29
+ ERROR: 'ERROR', // site candidate is discovered
30
+ };
31
+
32
+ /**
33
+ * Creates a new Site Candidate.
34
+ *
35
+ * @param {object} data - site candidate data
36
+ * @returns {Readonly<SiteCandidate>} new site candidate
37
+ */
38
+ const SiteCandidate = (data = {}) => {
39
+ const self = Base({
40
+ updatedBy: DEFAULT_UPDATED_BY,
41
+ ...data,
42
+ });
43
+ delete self.id; // no id property used in SiteCandidate modal
44
+
45
+ self.getBaseURL = () => self.state.baseURL;
46
+ self.getSiteId = () => self.state.siteId;
47
+ self.getSource = () => self.state.source;
48
+ self.getStatus = () => self.state.status;
49
+ self.getUpdatedBy = () => self.state.updatedBy;
50
+
51
+ self.setSiteId = (siteId) => {
52
+ self.state.siteId = siteId;
53
+ self.touch();
54
+ return self;
55
+ };
56
+
57
+ self.setSource = (source) => {
58
+ self.state.source = source;
59
+ self.touch();
60
+ return self;
61
+ };
62
+
63
+ self.setStatus = (status) => {
64
+ self.state.status = status;
65
+ self.touch();
66
+ return self;
67
+ };
68
+
69
+ self.setUpdatedBy = (updatedBy) => {
70
+ self.state.updatedBy = updatedBy;
71
+ self.touch();
72
+ return self;
73
+ };
74
+
75
+ return Object.freeze(self);
76
+ };
77
+
78
+ /**
79
+ * Creates a new Site Candidate.
80
+ *
81
+ * @param {object} data - site candidate data
82
+ * @returns {Readonly<SiteCandidate>} new site candidate
83
+ */
84
+ export const createSiteCandidate = (data) => {
85
+ const newState = { ...data };
86
+
87
+ if (!isValidUrl(newState.baseURL)) {
88
+ throw new Error('Base URL must be a valid URL');
89
+ }
90
+
91
+ if (!hasText(newState.updatedBy)) {
92
+ newState.updatedBy = DEFAULT_UPDATED_BY;
93
+ }
94
+
95
+ return SiteCandidate(newState);
96
+ };
@@ -13,6 +13,7 @@
13
13
  import { createClient } from '@adobe/spacecat-shared-dynamo';
14
14
  import { auditFunctions } from './audits/index.js';
15
15
  import { siteFunctions } from './sites/index.js';
16
+ import { siteCandidateFunctions } from './site-candidates/index.js';
16
17
  import { organizationFunctions } from './organizations/index.js';
17
18
 
18
19
  /**
@@ -30,11 +31,13 @@ export const createDataAccess = (config, log = console) => {
30
31
 
31
32
  const auditFuncs = auditFunctions(dynamoClient, config, log);
32
33
  const siteFuncs = siteFunctions(dynamoClient, config, log);
34
+ const siteCandidateFuncs = siteCandidateFunctions(dynamoClient, config, log);
33
35
  const organizationFuncs = organizationFunctions(dynamoClient, config, log);
34
36
 
35
37
  return {
36
38
  ...auditFuncs,
37
39
  ...siteFuncs,
40
+ ...siteCandidateFuncs,
38
41
  ...organizationFuncs,
39
42
  };
40
43
  };
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { isObject } from '@adobe/spacecat-shared-utils';
14
+ import { createSiteCandidate } from '../../models/site-candidate.js';
15
+ import { SiteCandidateDto } from '../../dto/site-candidate.js';
16
+
17
+ /**
18
+ * Checks if a site candidate exists in site candidates table using base url
19
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
20
+ * @param {DataAccessConfig} config - The data access config.
21
+ * @param {string} baseURL - base url of the site candidate.
22
+ * @return {Promise<Readonly<Boolean>>} A promise that resolves to true if site candidate exist
23
+ */
24
+ export const exists = async (dynamoClient, config, baseURL) => {
25
+ const dynamoItem = await dynamoClient.getItem(config.tableNameSiteCandidates, { baseURL });
26
+
27
+ return isObject(dynamoItem);
28
+ };
29
+
30
+ /**
31
+ * Upserts a site candidate.
32
+ *
33
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
34
+ * @param {DataAccessConfig} config - The data access config.
35
+ * @param {object} log - the logger object
36
+ * @param {object} siteCandidateData - The site candidate data.
37
+ * @returns {Promise<Readonly<SiteCandidate>>} newly created site candidate if hadn't created before
38
+ */
39
+ export const upsertSiteCandidate = async (
40
+ dynamoClient,
41
+ config,
42
+ log,
43
+ siteCandidateData,
44
+ ) => {
45
+ const siteCandidate = createSiteCandidate(siteCandidateData);
46
+ const siteCandidateExists = await exists(dynamoClient, config, siteCandidate.getBaseURL());
47
+
48
+ if (siteCandidateExists) {
49
+ log.info(`Ignoring the site candidate with base url ${siteCandidate.getBaseURL()} because it already exists`);
50
+ return siteCandidate;
51
+ }
52
+
53
+ await dynamoClient.putItem(
54
+ config.tableNameSiteCandidates,
55
+ SiteCandidateDto.toDynamoItem(siteCandidate),
56
+ );
57
+
58
+ return siteCandidate;
59
+ };
60
+
61
+ /**
62
+ * Updates a site candidate.
63
+ *
64
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
65
+ * @param {DataAccessConfig} config - The data access config.
66
+ * @param {SiteCandidate} siteCandidate - The site candidate object to be updated.
67
+ * @returns {Promise<Readonly<Site>>} - The updated site candidate.
68
+ */
69
+ export const updateSiteCandidate = async (
70
+ dynamoClient,
71
+ config,
72
+ siteCandidate,
73
+ ) => {
74
+ const siteCandidateExists = await exists(dynamoClient, config, siteCandidate.getBaseURL());
75
+
76
+ if (!siteCandidateExists) {
77
+ throw new Error(`Site candidate with base url ${siteCandidate.getBaseURL()} does not exist`);
78
+ }
79
+
80
+ await dynamoClient.putItem(
81
+ config.tableNameSiteCandidates,
82
+ SiteCandidateDto.toDynamoItem(siteCandidate),
83
+ );
84
+
85
+ return siteCandidate;
86
+ };
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright 2024 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {
14
+ upsertSiteCandidate,
15
+ exists,
16
+ updateSiteCandidate,
17
+ } from './accessPatterns.js';
18
+
19
+ export const siteCandidateFunctions = (dynamoClient, config, log) => ({
20
+ upsertSiteCandidate: (siteCandidateData) => upsertSiteCandidate(
21
+ dynamoClient,
22
+ config,
23
+ log,
24
+ siteCandidateData,
25
+ ),
26
+ siteCandidateExists: (baseUrl) => exists(
27
+ dynamoClient,
28
+ config,
29
+ baseUrl,
30
+ ),
31
+ updateSiteCandidate: (siteCandidateData) => updateSiteCandidate(
32
+ dynamoClient,
33
+ config,
34
+ siteCandidateData,
35
+ ),
36
+ });