@adobe/spacecat-shared-data-access 1.7.0 → 1.9.0

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.9.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.8.0...@adobe/spacecat-shared-data-access-v1.9.0) (2024-01-18)
2
+
3
+
4
+ ### Features
5
+
6
+ * data model for organizations ([944be83](https://github.com/adobe/spacecat-shared/commit/944be83c9f19e3acee9bb17352b3058fe6578a69))
7
+
8
+ # [@adobe/spacecat-shared-data-access-v1.8.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.7.0...@adobe/spacecat-shared-data-access-v1.8.0) (2024-01-18)
9
+
10
+
11
+ ### Features
12
+
13
+ * add broken-backlinks audit type (SITES-18417) ([#86](https://github.com/adobe/spacecat-shared/issues/86)) ([b16c366](https://github.com/adobe/spacecat-shared/commit/b16c3667808221958ff4a526512cb89c3cd0bbcb))
14
+
1
15
  # [@adobe/spacecat-shared-data-access-v1.7.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v1.6.4...@adobe/spacecat-shared-data-access-v1.7.0) (2024-01-18)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -33,6 +33,8 @@
33
33
  "@adobe/spacecat-shared-utils": "1.2.0",
34
34
  "@aws-sdk/client-dynamodb": "3.490.0",
35
35
  "@aws-sdk/lib-dynamodb": "3.490.0",
36
+ "@types/joi": "17.2.3",
37
+ "joi": "17.11.0",
36
38
  "uuid": "9.0.1"
37
39
  },
38
40
  "devDependencies": {
@@ -0,0 +1,52 @@
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 { Config } from '../models/site/config.js';
14
+ import { createOrganization } from '../models/organization.js';
15
+
16
+ /**
17
+ * Data transfer object for Organization.
18
+ */
19
+ export const OrganizationDto = {
20
+ /**
21
+ * Converts an Organization object into a DynamoDB item.
22
+ * @param {Readonly<Organization>} organization - Organization object.
23
+ * @returns {{createdAt, baseURL, GSI1PK: string, id, imsOrgId, updatedAt}}
24
+ */
25
+ toDynamoItem: (organization) => ({
26
+ id: organization.getId(),
27
+ name: organization.getName(),
28
+ imsOrgId: organization.getImsOrgId(),
29
+ createdAt: organization.getCreatedAt(),
30
+ updatedAt: organization.getUpdatedAt(),
31
+ GSI1PK: 'ALL_ORGANIZATIONS',
32
+ config: Config.toDynamoItem(organization.getConfig()),
33
+ }),
34
+
35
+ /**
36
+ * Converts a DynamoDB item into a Organization object.
37
+ * @param {object } dynamoItem - DynamoDB item.
38
+ * @returns {Readonly<Organization>} Organization object.
39
+ */
40
+ fromDynamoItem: (dynamoItem) => {
41
+ const organizationData = {
42
+ id: dynamoItem.id,
43
+ name: dynamoItem.name,
44
+ imsOrgId: dynamoItem.imsOrgId,
45
+ createdAt: dynamoItem.createdAt,
46
+ updatedAt: dynamoItem.updatedAt,
47
+ config: dynamoItem.config,
48
+ };
49
+
50
+ return createOrganization(organizationData);
51
+ },
52
+ };
package/src/dto/site.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { createSite } from '../models/site.js';
14
14
  import AuditConfig from '../models/site/audit-config.js';
15
+ import { Config } from '../models/site/config.js';
15
16
 
16
17
  /**
17
18
  * Data transfer object for Site.
@@ -20,20 +21,21 @@ export const SiteDto = {
20
21
  /**
21
22
  * Converts a Site object into a DynamoDB item.
22
23
  * @param {Readonly<Site>} site - Site object.
23
- * @returns {{createdAt, baseURL, GSI1PK: string, id, imsOrgId, updatedAt}}
24
+ * @returns {{createdAt, baseURL, GSI1PK: string, id, organizationId, updatedAt}}
24
25
  */
25
26
  toDynamoItem: (site) => ({
26
27
  id: site.getId(),
27
28
  baseURL: site.getBaseURL(),
28
29
  deliveryType: site.getDeliveryType(),
29
30
  gitHubURL: site.getGitHubURL() || '',
30
- imsOrgId: site.getImsOrgId() || '',
31
+ organizationId: site.getOrganizationId() || '',
31
32
  isLive: site.isLive(),
32
33
  isLiveToggledAt: site.getIsLiveToggledAt(),
33
34
  createdAt: site.getCreatedAt(),
34
35
  updatedAt: site.getUpdatedAt(),
35
36
  GSI1PK: 'ALL_SITES',
36
37
  auditConfig: AuditConfig.toDynamoItem(site.getAuditConfig()),
38
+ config: Config.toDynamoItem(site.getConfig()),
37
39
  }),
38
40
 
39
41
  /**
@@ -47,12 +49,13 @@ export const SiteDto = {
47
49
  baseURL: dynamoItem.baseURL,
48
50
  deliveryType: dynamoItem.deliveryType,
49
51
  gitHubURL: dynamoItem.gitHubURL,
50
- imsOrgId: dynamoItem.imsOrgId,
52
+ organizationId: dynamoItem.organizationId,
51
53
  isLive: dynamoItem.isLive,
52
54
  isLiveToggledAt: dynamoItem.isLiveToggledAt,
53
55
  createdAt: dynamoItem.createdAt,
54
56
  updatedAt: dynamoItem.updatedAt,
55
57
  auditConfig: dynamoItem.auditConfig,
58
+ config: dynamoItem.config,
56
59
  };
57
60
 
58
61
  return createSite(siteData);
package/src/index.d.ts CHANGED
@@ -96,6 +96,10 @@ export interface AuditConfigType {
96
96
  disabled: () => boolean;
97
97
  }
98
98
 
99
+ export interface Config {
100
+
101
+ }
102
+
99
103
  /**
100
104
  * AuditConfig defines the structure for the overall audit configuration of a site.
101
105
  */
@@ -150,10 +154,10 @@ export interface Site {
150
154
  getGitHubURL: () => string;
151
155
 
152
156
  /**
153
- * Retrieves the IMS Organization ID associated with the site.
154
- * @returns {string} The IMS Org ID.
157
+ * Retrieves the Organization ID associated with the site.
158
+ * @returns {string} The Org ID.
155
159
  */
156
- getImsOrgId: () => string;
160
+ getOrganizationId: () => string;
157
161
 
158
162
  /**
159
163
  * Retrieves the creation timestamp of the site.
@@ -173,6 +177,12 @@ export interface Site {
173
177
  */
174
178
  getAuditConfig: () => AuditConfig;
175
179
 
180
+ /**
181
+ * Retrieves the current configuration for the site.
182
+ * @returns {Config} The current configuration.
183
+ */
184
+ getConfig: () => Config;
185
+
176
186
  /**
177
187
  * Retrieves the audits associated with the site.
178
188
  * @returns {Audit[]} The list of audits.
@@ -213,10 +223,48 @@ export interface Site {
213
223
 
214
224
  /**
215
225
  * Updates the IMS Org ID of the site.
216
- * @param {string} imsOrgId The new IMS Org ID.
226
+ * @param {string} organizationId The new Org ID.
217
227
  * @returns {Site} The updated site instance.
218
228
  */
219
- updateImsOrgId: (imsOrgId: string) => Site;
229
+ updateOrganizationId: (organizationId: string) => Site;
230
+ }
231
+
232
+ export interface Organization {
233
+ /**
234
+ * Retrieves the ID of the site.
235
+ * @returns {string} The site ID.
236
+ */
237
+ getId: () => string;
238
+
239
+ /**
240
+ * Retrieves the base URL of the site.
241
+ * @returns {string} The base URL.
242
+ */
243
+ getName: () => string;
244
+
245
+ /**
246
+ * Retrieves the IMS Organization ID associated with the site.
247
+ * @returns {string} The IMS Org ID.
248
+ */
249
+ getImsOrgId: () => string;
250
+
251
+ /**
252
+ * Retrieves the creation timestamp of the site.
253
+ * @returns {string} The creation timestamp.
254
+ */
255
+ getCreatedAt: () => string;
256
+
257
+ /**
258
+ * Retrieves the last update timestamp of the site.
259
+ * @returns {string} The last update timestamp.
260
+ */
261
+ getUpdatedAt: () => string;
262
+
263
+ /**
264
+ * Retrieves the current audit configuration for the site.
265
+ * @returns {Config} The current audit configuration.
266
+ */
267
+ getConfig: () => Config;
220
268
  }
221
269
 
222
270
  export interface DataAccess {
@@ -257,6 +305,9 @@ export interface DataAccess {
257
305
  sortAuditsAscending?: boolean,
258
306
  deliveryType?: string,
259
307
  ) => Promise<Site[]>;
308
+ getSitesByOrganizationID: (
309
+ organizationId: string,
310
+ ) => Promise<Organization[]>
260
311
  getSiteByBaseURL: (
261
312
  baseUrl: string,
262
313
  ) => Promise<Site | null>;
@@ -285,17 +336,35 @@ export interface DataAccess {
285
336
  removeSite: (
286
337
  siteId: string,
287
338
  ) => Promise<void>;
339
+ getOrganizations:
340
+ () => Promise<Organization[]>;
341
+ getOrganizationByID: (
342
+ organizationID: string,
343
+ ) => Promise<Organization | null>;
344
+ addOrganization: (
345
+ organizationData: object,
346
+ ) => Promise<Organization>;
347
+ updateOrganization: (
348
+ organization: Organization,
349
+ ) => Promise<Organization>;
350
+ removeRemoveOrganization: (
351
+ organizationId: string,
352
+ ) => Promise<void>;
288
353
  }
289
354
 
290
355
  interface DataAccessConfig {
291
356
  tableNameAudits: string;
292
357
  tableNameLatestAudits: string;
358
+ tableNameOrganizations: string,
293
359
  tableNameSites: string;
294
360
  indexNameAllSites: string;
361
+ indexNameAllSitesOrganizations: string,
295
362
  indexNameAllSitesByDeliveryType: string;
296
363
  indexNameAllLatestAuditScores: string;
364
+ indexNameAllOrganizations: string,
297
365
  pkAllSites: string;
298
366
  pkAllLatestAudits: string;
367
+ pkAllOrganizations: string;
299
368
  }
300
369
 
301
370
  export function createDataAccess(
package/src/index.js CHANGED
@@ -15,12 +15,16 @@ 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_ORGANIZATIONS = 'spacecat-services-organizations-dev';
18
19
 
19
20
  const INDEX_NAME_ALL_SITES = 'spacecat-services-all-sites-dev';
21
+ const INDEX_NAME_ALL_ORGANIZATIONS = 'spacecat-services-all-organizations-dev';
20
22
  const INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE = 'spacecat-services-all-sites-by-delivery-type-dev';
21
23
  const INDEX_NAME_ALL_LATEST_AUDIT_SCORES = 'spacecat-services-all-latest-audit-scores-dev';
24
+ const INDEX_NAME_ALL_SITES_ORGANIZATIONS = 'spacecat-services-all-sites-organizations-dev';
22
25
 
23
26
  const PK_ALL_SITES = 'ALL_SITES';
27
+ const PK_ALL_ORGANIZATIONS = 'ALL_ORGANIZATIONS';
24
28
  const PK_ALL_LATEST_AUDITS = 'ALL_LATEST_AUDITS';
25
29
 
26
30
  export default function dataAccessWrapper(fn) {
@@ -32,19 +36,26 @@ export default function dataAccessWrapper(fn) {
32
36
  DYNAMO_TABLE_NAME_AUDITS = TABLE_NAME_AUDITS,
33
37
  DYNAMO_TABLE_NAME_LATEST_AUDITS = TABLE_NAME_LATEST_AUDITS,
34
38
  DYNAMO_TABLE_NAME_SITES = TABLE_NAME_SITES,
39
+ DYNAMO_TABLE_NAME_ORGANIZATIONS = TABLE_NAME_ORGANIZATIONS,
35
40
  DYNAMO_INDEX_NAME_ALL_SITES = INDEX_NAME_ALL_SITES,
36
41
  DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE = INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
37
42
  DYNAMO_INDEX_NAME_ALL_LATEST_AUDIT_SCORES = INDEX_NAME_ALL_LATEST_AUDIT_SCORES,
43
+ DYNAMO_INDEX_NAME_ALL_ORGANIZATIONS = INDEX_NAME_ALL_ORGANIZATIONS,
44
+ DYNAMO_INDEX_NAME_ALL_SITES_ORGANIZATIONS = INDEX_NAME_ALL_SITES_ORGANIZATIONS,
38
45
  } = context.env;
39
46
 
40
47
  context.dataAccess = createDataAccess({
41
48
  tableNameAudits: DYNAMO_TABLE_NAME_AUDITS,
42
49
  tableNameLatestAudits: DYNAMO_TABLE_NAME_LATEST_AUDITS,
50
+ tableNameOrganizations: DYNAMO_TABLE_NAME_ORGANIZATIONS,
43
51
  tableNameSites: DYNAMO_TABLE_NAME_SITES,
44
52
  indexNameAllSites: DYNAMO_INDEX_NAME_ALL_SITES,
53
+ indexNameOrganizations: DYNAMO_INDEX_NAME_ALL_ORGANIZATIONS,
45
54
  indexNameAllSitesByDeliveryType: DYNAMO_INDEX_NAME_ALL_SITES_BY_DELIVERY_TYPE,
46
55
  indexNameAllLatestAuditScores: DYNAMO_INDEX_NAME_ALL_LATEST_AUDIT_SCORES,
56
+ indexNameAllSitesOrganizations: DYNAMO_INDEX_NAME_ALL_SITES_ORGANIZATIONS,
47
57
  pkAllSites: PK_ALL_SITES,
58
+ pkAllOrganizations: PK_ALL_ORGANIZATIONS,
48
59
  pkAllLatestAudits: PK_ALL_LATEST_AUDITS,
49
60
  }, log);
50
61
  }
@@ -13,6 +13,7 @@
13
13
  import { hasText, isIsoDate, isObject } from '@adobe/spacecat-shared-utils';
14
14
  import { Base } from './base.js';
15
15
 
16
+ export const AUDIT_TYPE_BROKEN_BACKLINKS = 'broken-backlinks';
16
17
  export const AUDIT_TYPE_CWV = 'cwv';
17
18
  export const AUDIT_TYPE_LHS_DESKTOP = 'lhs-desktop';
18
19
  export const AUDIT_TYPE_LHS_MOBILE = 'lhs-mobile';
@@ -20,6 +21,7 @@ export const AUDIT_TYPE_LHS_MOBILE = 'lhs-mobile';
20
21
  const EXPIRES_IN_DAYS = 30;
21
22
 
22
23
  const AUDIT_TYPE_PROPERTIES = {
24
+ [AUDIT_TYPE_BROKEN_BACKLINKS]: [],
23
25
  [AUDIT_TYPE_CWV]: [],
24
26
  [AUDIT_TYPE_LHS_DESKTOP]: ['performance', 'seo', 'accessibility', 'best-practices'],
25
27
  [AUDIT_TYPE_LHS_MOBILE]: ['performance', 'seo', 'accessibility', 'best-practices'],
@@ -36,7 +38,7 @@ const validateScores = (auditResult, auditType) => {
36
38
  return true;
37
39
  }
38
40
 
39
- if (!isObject(auditResult.scores)) {
41
+ if (auditType !== AUDIT_TYPE_BROKEN_BACKLINKS && !isObject(auditResult.scores)) {
40
42
  throw new Error(`Missing scores property for audit type '${auditType}'`);
41
43
  }
42
44
 
@@ -0,0 +1,100 @@
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, isObject } from '@adobe/spacecat-shared-utils';
14
+
15
+ import { Base } from './base.js';
16
+ import { Config, DEFAULT_CONFIG } from './site/config.js';
17
+
18
+ /**
19
+ * Creates a new Organization.
20
+ *
21
+ * @param {object} data - organization data
22
+ * @returns {Readonly<Organization>} organization - new organization
23
+ */
24
+ const Organization = (data = {}) => {
25
+ const self = Base(data);
26
+
27
+ self.getConfig = () => self.state.config;
28
+ self.getName = () => self.state.name;
29
+ self.getImsOrgId = () => self.state.imsOrgId;
30
+
31
+ /**
32
+ * Updates the IMS Org ID belonging to the organization.
33
+ * @param {string} imsOrgId - The IMS Org ID.
34
+ * @return {Base} The updated organization.
35
+ */
36
+ self.updateImsOrgId = (imsOrgId) => {
37
+ if (!hasText(imsOrgId)) {
38
+ throw new Error('IMS Org ID must be provided');
39
+ }
40
+
41
+ self.state.imsOrgId = imsOrgId;
42
+ self.touch();
43
+
44
+ return self;
45
+ };
46
+
47
+ /**
48
+ * Updates the organization name.
49
+ * @param {string} name - The name of the organization.
50
+ * @return {Base} The updated organization.
51
+ */
52
+ self.updateName = (name) => {
53
+ if (!hasText(name)) {
54
+ throw new Error('Org name must be provided');
55
+ }
56
+
57
+ self.state.name = name;
58
+ self.touch();
59
+
60
+ return self;
61
+ };
62
+
63
+ /**
64
+ * Updates the organization config.
65
+ * @param {string} config - The Org config.
66
+ * @return {Base} The updated organization.
67
+ */
68
+ self.updateConfig = (config) => {
69
+ if (!isObject(config)) {
70
+ throw new Error('Config must be provided');
71
+ }
72
+
73
+ self.state.config = Config.toDynamoItem(config);
74
+ self.touch();
75
+
76
+ return self;
77
+ };
78
+ return Object.freeze(self);
79
+ };
80
+
81
+ /**
82
+ * Creates a new Organization.
83
+ *
84
+ * @param {object} data - organization data
85
+ * @returns {Readonly<Organization>} organization - new organization
86
+ */
87
+ export const createOrganization = (data) => {
88
+ const newState = { ...data };
89
+
90
+ if (!isObject(newState.config)) {
91
+ newState.config = { ...DEFAULT_CONFIG };
92
+ }
93
+ if (!hasText(newState.name)) {
94
+ throw new Error('Org name must be provided');
95
+ }
96
+
97
+ newState.config = Config(newState.config);
98
+
99
+ return Organization(newState);
100
+ };
@@ -0,0 +1,64 @@
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 Joi from 'joi';
14
+
15
+ export const configSchema = Joi.object({
16
+ slack: Joi.object({
17
+ workspace: Joi.string(),
18
+ channel: Joi.string(),
19
+ }),
20
+ alerts: Joi.array().items(Joi.object({
21
+ type: Joi.string(),
22
+ byOrg: Joi.boolean(),
23
+ mentions: Joi.array().items(Joi.object({ slack: Joi.array().items(Joi.string()) })),
24
+ })),
25
+ });
26
+
27
+ export const DEFAULT_CONFIG = {
28
+ slack: {
29
+ },
30
+ alerts: [],
31
+ };
32
+
33
+ // Function to validate incoming configuration
34
+ function validateConfiguration(config) {
35
+ const { error, value } = configSchema.validate(config);
36
+
37
+ if (error) {
38
+ throw new Error(`Configuration validation error: ${error.message}`);
39
+ }
40
+
41
+ return value; // Validated and sanitized configuration
42
+ }
43
+
44
+ export const Config = (data = {}) => {
45
+ const validConfig = validateConfiguration(data);
46
+ const state = {
47
+ slack: {
48
+ ...(validConfig?.slack?.channel ? { channel: validConfig?.slack?.channel } : {}),
49
+ ...(validConfig?.slack?.workspace ? { workspace: validConfig?.slack?.workspace } : {}),
50
+ },
51
+ alerts: validConfig.alerts || [],
52
+ };
53
+
54
+ const self = {
55
+ alerts: state.alerts,
56
+ slack: state.slack,
57
+ };
58
+
59
+ return Object.freeze(self);
60
+ };
61
+
62
+ Config.fromDynamoItem = (dynamoItem) => Config(dynamoItem);
63
+
64
+ Config.toDynamoItem = (config) => validateConfiguration(config);
@@ -10,10 +10,11 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { hasText, isObject, isValidUrl } from '@adobe/spacecat-shared-utils';
13
+ import { isObject, isValidUrl } from '@adobe/spacecat-shared-utils';
14
14
 
15
15
  import { Base } from './base.js';
16
16
  import AuditConfig from './site/audit-config.js';
17
+ import { Config, DEFAULT_CONFIG } from './site/config.js';
17
18
 
18
19
  export const DELIVERY_TYPES = {
19
20
  AEM_CS: 'aem_cs',
@@ -35,9 +36,10 @@ const Site = (data = {}) => {
35
36
  self.getAuditConfig = () => self.state.auditConfig;
36
37
  self.getAudits = () => self.state.audits;
37
38
  self.getBaseURL = () => self.state.baseURL;
39
+ self.getConfig = () => self.state.config;
38
40
  self.getDeliveryType = () => self.state.deliveryType;
39
41
  self.getGitHubURL = () => self.state.gitHubURL;
40
- self.getImsOrgId = () => self.state.imsOrgId;
42
+ self.getOrganizationId = () => self.state.organizationId;
41
43
  self.isLive = () => self.state.isLive;
42
44
  self.getIsLiveToggledAt = () => self.state.isLiveToggledAt;
43
45
 
@@ -118,16 +120,12 @@ const Site = (data = {}) => {
118
120
  };
119
121
 
120
122
  /**
121
- * Updates the IMS Org ID belonging to the site.
122
- * @param {string} imsOrgId - The IMS Org ID.
123
+ * Updates the organizationId the site belongs to.
124
+ * @param {string} organizationId - The Org ID.
123
125
  * @return {Base} The updated site.
124
126
  */
125
- self.updateImsOrgId = (imsOrgId) => {
126
- if (!hasText(imsOrgId)) {
127
- throw new Error('IMS Org ID must be provided');
128
- }
129
-
130
- self.state.imsOrgId = imsOrgId;
127
+ self.updateOrganizationId = (organizationId) => {
128
+ self.state.organizationId = organizationId;
131
129
  self.touch();
132
130
 
133
131
  return self;
@@ -186,5 +184,11 @@ export const createSite = (data) => {
186
184
 
187
185
  newState.auditConfig = AuditConfig(newState.auditConfig);
188
186
 
187
+ if (!isObject(newState.config)) {
188
+ newState.config = { ...DEFAULT_CONFIG };
189
+ }
190
+
191
+ newState.config = Config(newState.config);
192
+
189
193
  return Site(newState);
190
194
  };
@@ -13,11 +13,15 @@
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 { organizationFunctions } from './organizations/index.js';
16
17
 
17
18
  /**
18
19
  * Creates a data access object.
19
20
  *
20
- * @param {DataAccessConfig} config configuration
21
+ * @param {{pkAllSites: string, pkAllLatestAudits: string, indexNameAllLatestAuditScores: string,
22
+ * tableNameAudits: string,tableNameLatestAudits: string, indexNameAllSitesOrganizations: string,
23
+ * tableNameSites: string, tableNameOrganizations: string, indexNameAllSites: string,
24
+ * indexNameAllOrganizations: string, pkAllOrganizations: string}} config configuration
21
25
  * @param {Logger} log logger
22
26
  * @returns {object} data access object
23
27
  */
@@ -26,9 +30,11 @@ export const createDataAccess = (config, log = console) => {
26
30
 
27
31
  const auditFuncs = auditFunctions(dynamoClient, config, log);
28
32
  const siteFuncs = siteFunctions(dynamoClient, config, log);
33
+ const organizationFuncs = organizationFunctions(dynamoClient, config, log);
29
34
 
30
35
  return {
31
36
  ...auditFuncs,
32
37
  ...siteFuncs,
38
+ ...organizationFuncs,
33
39
  };
34
40
  };
@@ -0,0 +1,131 @@
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
+
15
+ import { createOrganization } from '../../models/organization.js';
16
+ import { OrganizationDto } from '../../dto/organization.js';
17
+
18
+ /**
19
+ * Retrieves all organizations.
20
+ *
21
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
22
+ * @param {DataAccessConfig} config - The data access config.
23
+ * @returns {Promise<Readonly<Organization>[]>} A promise that resolves to an array of all
24
+ * organizations.
25
+ */
26
+ export const getOrganizations = async (dynamoClient, config) => {
27
+ const dynamoItems = await dynamoClient.query({
28
+ TableName: config.tableNameOrganizations,
29
+ IndexName: config.indexNameAllOrganizations,
30
+ KeyConditionExpression: 'GSI1PK = :gsi1pk',
31
+ ExpressionAttributeValues: {
32
+ ':gsi1pk': config.pkAllOrganizations,
33
+ },
34
+ });
35
+
36
+ return dynamoItems.map((dynamoItem) => OrganizationDto.fromDynamoItem(dynamoItem));
37
+ };
38
+
39
+ export const getOrganizationByID = async (
40
+ dynamoClient,
41
+ config,
42
+ organizationId,
43
+ ) => {
44
+ const dynamoItem = await dynamoClient.getItem(
45
+ config.tableNameOrganizations,
46
+ { id: organizationId },
47
+ );
48
+
49
+ return isObject(dynamoItem) ? OrganizationDto.fromDynamoItem(dynamoItem) : null;
50
+ };
51
+
52
+ /**
53
+ * Adds an organization.
54
+ *
55
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
56
+ * @param {DataAccessConfig} config - The data access config.
57
+ * @param {Logger} log - The logger.
58
+ * @param {object} organizationData - The organization data.
59
+ * @returns {Promise<Readonly<Organization>>}
60
+ */
61
+ export const addOrganization = async (
62
+ dynamoClient,
63
+ config,
64
+ log,
65
+ organizationData,
66
+ ) => {
67
+ const organization = createOrganization(organizationData);
68
+
69
+ await dynamoClient.putItem(
70
+ config.tableNameOrganizations,
71
+ OrganizationDto.toDynamoItem(organization),
72
+ );
73
+
74
+ return organization;
75
+ };
76
+
77
+ /**
78
+ * Updates an organization.
79
+ *
80
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
81
+ * @param {DataAccessConfig} config - The data access config.
82
+ * @param {Logger} log - The logger.
83
+ * @param {Organization} organization - The organization.
84
+ * @returns {Promise<Readonly<Organization>>} - The updated organization.
85
+ */
86
+ export const updateOrganization = async (
87
+ dynamoClient,
88
+ config,
89
+ log,
90
+ organization,
91
+ ) => {
92
+ const existingOrganization = await getOrganizationByID(
93
+ dynamoClient,
94
+ config,
95
+ organization.getId(),
96
+ );
97
+
98
+ if (!isObject(existingOrganization)) {
99
+ throw new Error('Organization not found');
100
+ }
101
+
102
+ await dynamoClient.putItem(
103
+ config.tableNameOrganizations,
104
+ OrganizationDto.toDynamoItem(organization),
105
+ );
106
+
107
+ return organization;
108
+ };
109
+
110
+ /**
111
+ * Removes an organization.
112
+ *
113
+ * @param {DynamoDbClient} dynamoClient - The DynamoDB client.
114
+ * @param {DataAccessConfig} config - The data access config.
115
+ * @param {Logger} log - The logger.
116
+ * @param {string} organizationId - The ID of the organization to remove.
117
+ * @returns {Promise<void>}
118
+ */
119
+ export const removeOrganization = async (
120
+ dynamoClient,
121
+ config,
122
+ log,
123
+ organizationId,
124
+ ) => {
125
+ try {
126
+ await dynamoClient.removeItem(config.tableNameOrganizations, { id: organizationId });
127
+ } catch (error) {
128
+ log.error(`Error removing organization: ${error.message}`);
129
+ throw error;
130
+ }
131
+ };
@@ -0,0 +1,45 @@
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
+ import {
13
+ getOrganizations,
14
+ getOrganizationByID,
15
+ addOrganization,
16
+ updateOrganization,
17
+ removeOrganization,
18
+ } from './accessPatterns.js';
19
+
20
+ export const organizationFunctions = (dynamoClient, config, log) => ({
21
+ getOrganizations: () => getOrganizations(dynamoClient, config),
22
+ getOrganizationByID: (organizationId) => getOrganizationByID(
23
+ dynamoClient,
24
+ config,
25
+ organizationId,
26
+ ),
27
+ addOrganization: (organizationData) => addOrganization(
28
+ dynamoClient,
29
+ config,
30
+ log,
31
+ organizationData,
32
+ ),
33
+ updateOrganization: (organizationData) => updateOrganization(
34
+ dynamoClient,
35
+ config,
36
+ log,
37
+ organizationData,
38
+ ),
39
+ removeOrganization: (organizationId) => removeOrganization(
40
+ dynamoClient,
41
+ config,
42
+ log,
43
+ organizationId,
44
+ ),
45
+ });
@@ -247,6 +247,24 @@ export const getSiteByBaseURLWithLatestAudit = async (
247
247
  auditType,
248
248
  ) => getSiteByBaseURLWithAuditInfo(dynamoClient, config, log, baseUrl, auditType, true);
249
249
 
250
+ export const getSitesByOrganizationID = async (
251
+ dynamoClient,
252
+ config,
253
+ organizationId,
254
+ ) => {
255
+ const dynamoItems = await dynamoClient.query({
256
+ TableName: config.tableNameSites,
257
+ IndexName: config.indexNameAllSitesOrganizations,
258
+ KeyConditionExpression: 'organizationId = :organizationId',
259
+ ExpressionAttributeValues: {
260
+ ':organizationId': organizationId,
261
+ },
262
+ Limit: 1,
263
+ });
264
+
265
+ return dynamoItems.map((dynamoItem) => SiteDto.fromDynamoItem(dynamoItem));
266
+ };
267
+
250
268
  /**
251
269
  * Retrieves a site by its ID.
252
270
  *
@@ -17,7 +17,7 @@ import {
17
17
  getSiteByBaseURLWithAudits,
18
18
  getSiteByBaseURLWithLatestAudit,
19
19
  getSiteByID,
20
- getSites, getSitesByDeliveryType,
20
+ getSites, getSitesByDeliveryType, getSitesByOrganizationID,
21
21
  getSitesToAudit,
22
22
  getSitesWithLatestAudit, removeSite,
23
23
  updateSite,
@@ -33,6 +33,11 @@ export const siteFunctions = (dynamoClient, config, log) => ({
33
33
  config,
34
34
  deliveryType,
35
35
  ),
36
+ getSitesByOrganizationID: (organizationId) => getSitesByOrganizationID(
37
+ dynamoClient,
38
+ config,
39
+ organizationId,
40
+ ),
36
41
  getSitesToAudit: () => getSitesToAudit(
37
42
  dynamoClient,
38
43
  config,