@adobe/spacecat-shared-data-access 1.59.2 → 1.60.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +2 -2
  3. package/src/models/site/config.js +1 -1
  4. package/src/service/audits/accessPatterns.js +7 -7
  5. package/src/service/experiments/accessPatterns.js +2 -2
  6. package/src/service/import-job/accessPatterns.js +1 -1
  7. package/src/service/import-url/accessPatterns.js +2 -2
  8. package/src/service/index.js +10 -18
  9. package/src/service/key-events/accessPatterns.js +3 -3
  10. package/src/service/organizations/accessPatterns.js +3 -3
  11. package/src/service/site-candidates/accessPatterns.js +1 -1
  12. package/src/service/sites/accessPatterns.js +11 -11
  13. package/src/v2/models/api-key/api-key.collection.js +26 -0
  14. package/src/v2/models/api-key/api-key.model.js +59 -0
  15. package/src/v2/models/api-key/api-key.schema.js +82 -0
  16. package/src/v2/models/api-key/index.d.ts +37 -0
  17. package/src/v2/models/api-key/index.js +19 -0
  18. package/src/v2/models/audit/audit.collection.js +26 -0
  19. package/src/v2/models/audit/audit.model.js +89 -0
  20. package/src/v2/models/audit/audit.schema.js +66 -0
  21. package/src/v2/models/audit/index.d.ts +40 -0
  22. package/src/v2/models/audit/index.js +19 -0
  23. package/src/v2/models/base/base.collection.js +450 -0
  24. package/src/v2/models/{base.model.js → base/base.model.js} +109 -89
  25. package/src/v2/models/base/constants.js +17 -0
  26. package/src/v2/models/base/entity.registry.js +137 -0
  27. package/src/v2/models/base/index.d.ts +83 -0
  28. package/src/v2/models/base/index.js +27 -0
  29. package/src/v2/models/base/reference.js +159 -0
  30. package/src/v2/models/base/schema.builder.js +420 -0
  31. package/src/v2/models/base/schema.js +283 -0
  32. package/src/v2/models/configuration/configuration.collection.js +39 -0
  33. package/src/v2/models/configuration/configuration.model.js +160 -0
  34. package/src/v2/models/configuration/configuration.schema.js +103 -0
  35. package/src/v2/models/configuration/index.d.ts +111 -0
  36. package/src/v2/models/configuration/index.js +19 -0
  37. package/src/v2/models/experiment/experiment.collection.js +26 -0
  38. package/src/v2/models/experiment/experiment.model.js +28 -0
  39. package/src/v2/models/experiment/experiment.schema.js +70 -0
  40. package/src/v2/models/experiment/index.d.ts +49 -0
  41. package/src/v2/models/experiment/index.js +19 -0
  42. package/src/v2/models/import-job/import-job.collection.js +45 -0
  43. package/src/v2/models/import-job/import-job.model.js +55 -0
  44. package/src/v2/models/import-job/import-job.schema.js +152 -0
  45. package/src/v2/models/import-job/index.d.ts +51 -0
  46. package/src/v2/models/import-job/index.js +19 -0
  47. package/src/v2/models/import-url/import-url.collection.js +26 -0
  48. package/src/v2/models/import-url/import-url.model.js +28 -0
  49. package/src/v2/models/import-url/import-url.schema.js +59 -0
  50. package/src/v2/models/import-url/index.d.ts +35 -0
  51. package/src/v2/models/import-url/index.js +19 -0
  52. package/src/v2/models/index.d.ts +11 -99
  53. package/src/v2/models/index.js +14 -15
  54. package/src/v2/models/key-event/index.d.ts +28 -0
  55. package/src/v2/models/key-event/index.js +19 -0
  56. package/src/v2/models/key-event/key-event.collection.js +26 -0
  57. package/src/v2/models/key-event/key-event.model.js +37 -0
  58. package/src/v2/models/key-event/key-event.schema.js +45 -0
  59. package/src/v2/models/opportunity/index.d.ts +46 -0
  60. package/src/v2/models/opportunity/index.js +19 -0
  61. package/src/v2/models/opportunity/opportunity.collection.js +26 -0
  62. package/src/v2/models/{opportunity.model.js → opportunity/opportunity.model.js} +15 -2
  63. package/src/v2/models/opportunity/opportunity.schema.js +69 -0
  64. package/src/v2/models/organization/index.d.ts +28 -0
  65. package/src/v2/models/organization/index.js +19 -0
  66. package/src/v2/models/organization/organization.collection.js +26 -0
  67. package/src/v2/models/organization/organization.model.js +31 -0
  68. package/src/v2/models/organization/organization.schema.js +51 -0
  69. package/src/v2/models/site/index.d.ts +43 -0
  70. package/src/v2/models/site/index.js +20 -0
  71. package/src/v2/models/site/site.collection.js +28 -0
  72. package/src/v2/models/site/site.model.js +47 -0
  73. package/src/v2/models/site/site.schema.js +91 -0
  74. package/src/v2/models/site-candidate/index.d.ts +38 -0
  75. package/src/v2/models/site-candidate/index.js +19 -0
  76. package/src/v2/models/site-candidate/site-candidate.collection.js +27 -0
  77. package/src/v2/models/site-candidate/site-candidate.model.js +41 -0
  78. package/src/v2/models/site-candidate/site-candidate.schema.js +59 -0
  79. package/src/v2/models/site-top-page/index.d.ts +35 -0
  80. package/src/v2/models/site-top-page/index.js +19 -0
  81. package/src/v2/models/site-top-page/site-top-page.collection.js +44 -0
  82. package/src/v2/models/site-top-page/site-top-page.model.js +28 -0
  83. package/src/v2/models/site-top-page/site-top-page.schema.js +65 -0
  84. package/src/v2/models/suggestion/index.d.ts +34 -0
  85. package/src/v2/models/suggestion/index.js +19 -0
  86. package/src/v2/models/suggestion/suggestion.collection.js +55 -0
  87. package/src/v2/models/{suggestion.model.js → suggestion/suggestion.model.js} +16 -1
  88. package/src/v2/models/suggestion/suggestion.schema.js +53 -0
  89. package/src/v2/readme.md +201 -256
  90. package/src/v2/util/accessor.utils.js +158 -0
  91. package/src/v2/util/guards.d.ts +7 -0
  92. package/src/v2/util/guards.js +21 -4
  93. package/src/v2/util/index.js +1 -0
  94. package/src/v2/util/patcher.js +54 -25
  95. package/src/v2/util/util.js +84 -0
  96. package/src/v2/models/base.collection.js +0 -275
  97. package/src/v2/models/model.factory.js +0 -74
  98. package/src/v2/models/opportunity.collection.js +0 -74
  99. package/src/v2/models/suggestion.collection.js +0 -104
  100. package/src/v2/schema/opportunity.schema.js +0 -159
  101. package/src/v2/schema/suggestion.schema.js +0 -132
  102. package/src/v2/util/reference.js +0 -41
@@ -0,0 +1,283 @@
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, isNonEmptyObject } from '@adobe/spacecat-shared-utils';
14
+
15
+ import {
16
+ classExtends,
17
+ entityNameToCollectionName,
18
+ entityNameToIdName,
19
+ isNonEmptyArray,
20
+ isPositiveInteger,
21
+ keyNamesToMethodName,
22
+ modelNameToEntityName,
23
+ } from '../../util/util.js';
24
+
25
+ import BaseCollection from './base.collection.js';
26
+ import BaseModel from './base.model.js';
27
+ import { INDEX_TYPES } from './constants.js';
28
+ import Reference from './reference.js';
29
+
30
+ class Schema {
31
+ /**
32
+ * Constructs a new Schema instance.
33
+ * @constructor
34
+ * @param {BaseModel} modelClass - The class representing the model.
35
+ * @param {BaseCollection} collectionClass - The class representing the model collection.
36
+ * @param {object} rawSchema - The raw schema data.
37
+ * @param {string} rawSchema.serviceName - The name of the service.
38
+ * @param {number} rawSchema.schemaVersion - The version of the schema.
39
+ * @param {object} rawSchema.attributes - The attributes of the schema.
40
+ * @param {object} rawSchema.indexes - The indexes of the schema.
41
+ * @param {Reference[]} [rawSchema.references] - The references of the schema.
42
+ */
43
+ constructor(
44
+ modelClass,
45
+ collectionClass,
46
+ rawSchema,
47
+ ) {
48
+ this.modelClass = modelClass;
49
+ this.collectionClass = collectionClass;
50
+
51
+ this.serviceName = rawSchema.serviceName;
52
+ this.schemaVersion = rawSchema.schemaVersion;
53
+ this.attributes = rawSchema.attributes;
54
+ this.indexes = rawSchema.indexes;
55
+ this.references = rawSchema.references || [];
56
+
57
+ this.#validateSchema();
58
+ }
59
+
60
+ #validateSchema() {
61
+ if (!classExtends(this.modelClass, BaseModel)) {
62
+ throw new Error('Model class must extend BaseModel');
63
+ }
64
+
65
+ if (!classExtends(this.collectionClass, BaseCollection)) {
66
+ throw new Error('Collection class must extend BaseCollection');
67
+ }
68
+
69
+ if (!hasText(this.serviceName)) {
70
+ throw new Error('Schema must have a service name');
71
+ }
72
+
73
+ if (!isPositiveInteger(this.schemaVersion)) {
74
+ throw new Error('Schema version must be a positive integer');
75
+ }
76
+
77
+ if (!isNonEmptyObject(this.attributes)) {
78
+ throw new Error('Schema must have attributes');
79
+ }
80
+
81
+ if (!isNonEmptyObject(this.indexes)) {
82
+ throw new Error('Schema must have indexes');
83
+ }
84
+
85
+ if (!Array.isArray(this.references)) {
86
+ throw new Error('References must be an array');
87
+ }
88
+ }
89
+
90
+ getAttribute(name) {
91
+ return this.attributes[name];
92
+ }
93
+
94
+ getAttributes() {
95
+ return this.attributes;
96
+ }
97
+
98
+ getCollectionName() {
99
+ return this.collectionClass.name;
100
+ }
101
+
102
+ getEntityName() {
103
+ return modelNameToEntityName(this.getModelName());
104
+ }
105
+
106
+ getIdName() {
107
+ return entityNameToIdName(this.getModelName());
108
+ }
109
+
110
+ /**
111
+ * Returns a data structure describing all index-based accessors (like allByX, findByX).
112
+ * This can then be used by BaseCollection to create methods without duplicating logic.
113
+ * @return {Array<{indexName: string, keySets: string[][]}>}
114
+ * Example: [
115
+ * { indexName: 'byOpportunityId', keySets: [['opportunityId'], ['opportunityId','status']] },
116
+ * { indexName: 'byStatusAndCreatedAt', keySets: [['status'],['status','createdAt']] }
117
+ * ]
118
+ */
119
+ getIndexAccessors() {
120
+ const indexes = this.getIndexes([INDEX_TYPES.PRIMARY]);
121
+ const result = [];
122
+
123
+ Object.keys(indexes).forEach((indexName) => {
124
+ const indexKeys = this.getIndexKeys(indexName);
125
+
126
+ if (!isNonEmptyArray(indexKeys)) return;
127
+
128
+ const keySets = [];
129
+ for (let i = 1; i <= indexKeys.length; i += 1) {
130
+ keySets.push(indexKeys.slice(0, i));
131
+ }
132
+
133
+ result.push({ indexName, keySets });
134
+ });
135
+
136
+ return result;
137
+ }
138
+
139
+ getIndexByName(indexName) {
140
+ return this.indexes[indexName];
141
+ }
142
+
143
+ /**
144
+ * Returns the indexes for the schema. By default, this returns all indexes.
145
+ * You can use the `exclude` parameter to exclude certain indexes.
146
+ * @param {Array<string>} [exclude] - One of the INDEX_TYPES values.
147
+ * @return {object} The indexes.
148
+ */
149
+ getIndexes(exclude) {
150
+ if (!Array.isArray(exclude)) {
151
+ return this.indexes;
152
+ }
153
+
154
+ return Object.keys(this.indexes).reduce((acc, indexName) => {
155
+ const index = this.indexes[indexName];
156
+
157
+ if (!exclude.includes(indexName)) {
158
+ acc[indexName] = index;
159
+ }
160
+
161
+ return acc;
162
+ }, {});
163
+ }
164
+
165
+ getIndexKeys(indexName) {
166
+ const index = this.getIndexByName(indexName);
167
+
168
+ if (!isNonEmptyObject(index)) {
169
+ return [];
170
+ }
171
+
172
+ const pkKeys = Array.isArray(index.pk?.facets) ? index.pk.facets : [];
173
+ const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [index.sk?.field];
174
+
175
+ return [...pkKeys, ...skKeys];
176
+ }
177
+
178
+ getModelClass() {
179
+ return this.modelClass;
180
+ }
181
+
182
+ getModelName() {
183
+ return this.modelClass.name;
184
+ }
185
+
186
+ /**
187
+ * Given a type and a target model name, returns the reciprocal reference if it exists.
188
+ * For example, if we have a has_many reference from Foo to Bar, this method can help find
189
+ * the belongs_to reference in Bar that points back to Foo.
190
+ * @param {EntityRegistry} registry - The entity registry.
191
+ * @param {Reference} reference - The reference to find the reciprocal for.
192
+ * @return {Reference|null} - The reciprocal reference or null if not found.
193
+ */
194
+ getReciprocalReference(registry, reference) {
195
+ const target = reference.getTarget();
196
+ const type = reference.getType();
197
+
198
+ if (type !== Reference.TYPES.HAS_MANY) {
199
+ return null;
200
+ }
201
+
202
+ const targetSchema = registry.getCollection(entityNameToCollectionName(target)).schema;
203
+
204
+ return targetSchema.getReferenceByTypeAndTarget(
205
+ Reference.TYPES.BELONGS_TO,
206
+ this.getModelName(),
207
+ );
208
+ }
209
+
210
+ getReferences() {
211
+ return this.references;
212
+ }
213
+
214
+ getReferencesByType(type) {
215
+ return this.references.filter((ref) => ref.type === type);
216
+ }
217
+
218
+ getReferenceByTypeAndTarget(type, target) {
219
+ return this.references.find((ref) => ref.type === type && ref.target === target);
220
+ }
221
+
222
+ getServiceName() {
223
+ return this.serviceName;
224
+ }
225
+
226
+ getVersion() {
227
+ return this.schemaVersion;
228
+ }
229
+
230
+ toAccessorConfigs(entity, log) {
231
+ const indexAccessors = this.getIndexAccessors();
232
+ const accessorConfigs = [];
233
+
234
+ indexAccessors.forEach(({ indexName, keySets }) => {
235
+ // generate a method for each prefix of the keySets array
236
+ // for example, if keySets = ['opportunityId', 'status'], we create:
237
+ // allByOpportunityId(...)
238
+ // findByOpportunityId(...)
239
+ // allByOpportunityIdAndStatus(...)
240
+ // findByOpportunityIdAndStatus(...)
241
+ keySets.forEach((subset) => {
242
+ accessorConfigs.push({
243
+ context: entity,
244
+ collection: entity,
245
+ name: keyNamesToMethodName(subset, 'allBy'),
246
+ requiredKeys: subset,
247
+ all: true,
248
+ });
249
+
250
+ accessorConfigs.push({
251
+ context: entity,
252
+ collection: entity,
253
+ name: keyNamesToMethodName(subset, 'findBy'),
254
+ requiredKeys: subset,
255
+ });
256
+
257
+ log.info(`Created accessors for index [${indexName}] with keys [${subset.join(', ')}]`);
258
+ });
259
+ });
260
+
261
+ return accessorConfigs;
262
+ }
263
+
264
+ /**
265
+ * Transforms the stored schema model into a format directly usable by ElectroDB.
266
+ * Here, you could do any final adjustments or transformations needed before returning.
267
+ *
268
+ * @returns {object} ElectroDB-compatible schema.
269
+ */
270
+ toElectroDBSchema() {
271
+ return {
272
+ model: {
273
+ entity: this.getModelName(),
274
+ version: String(this.getVersion()),
275
+ service: this.getServiceName(),
276
+ },
277
+ attributes: this.attributes,
278
+ indexes: this.indexes,
279
+ };
280
+ }
281
+ }
282
+
283
+ export default Schema;
@@ -0,0 +1,39 @@
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 { incrementVersion, sanitizeIdAndAuditFields } from '../../util/util.js';
14
+ import BaseCollection from '../base/base.collection.js';
15
+
16
+ /**
17
+ * ConfigurationCollection - A collection class responsible for managing Configuration entities.
18
+ * Extends the BaseCollection to provide specific methods for interacting with
19
+ * Configuration records.
20
+ *
21
+ * @class ConfigurationCollection
22
+ * @extends BaseCollection
23
+ */
24
+ class ConfigurationCollection extends BaseCollection {
25
+ async create(data) {
26
+ const latestConfiguration = await this.findLatest();
27
+ const version = latestConfiguration ? incrementVersion(latestConfiguration.getVersion()) : 1;
28
+ const sanitizedData = sanitizeIdAndAuditFields('Organization', data);
29
+ sanitizedData.version = version;
30
+
31
+ return super.create(sanitizedData);
32
+ }
33
+
34
+ async findLatest() {
35
+ return this.findByAll({}, { order: 'desc' });
36
+ }
37
+ }
38
+
39
+ export default ConfigurationCollection;
@@ -0,0 +1,160 @@
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 { isNonEmptyObject } from '@adobe/spacecat-shared-utils';
14
+
15
+ import { sanitizeIdAndAuditFields } from '../../util/util.js';
16
+ import BaseModel from '../base/base.model.js';
17
+
18
+ /**
19
+ * Configuration - A class representing an Configuration entity.
20
+ * Provides methods to access and manipulate Configuration-specific data.
21
+ *
22
+ * @class Configuration
23
+ * @extends BaseModel
24
+ */
25
+ class Configuration extends BaseModel {
26
+ // add your custom methods or overrides here
27
+
28
+ getHandler(type) {
29
+ return this.getHandlers()?.[type];
30
+ }
31
+
32
+ addHandler = (type, handlerData) => {
33
+ const handlers = this.getHandlers() || {};
34
+ handlers[type] = { ...handlerData };
35
+
36
+ this.setHandlers(handlers);
37
+ };
38
+
39
+ getSlackRoleMembersByRole(role) {
40
+ return this.getSlackRoles()?.[role] || [];
41
+ }
42
+
43
+ getEnabledSiteIdsForHandler(type) {
44
+ return this.getHandler(type)?.enabled?.sites || [];
45
+ }
46
+
47
+ isHandlerEnabledForSite(type, site) {
48
+ const handler = this.getHandlers()?.[type];
49
+ if (!handler) return false;
50
+
51
+ const siteId = site.getId();
52
+ const orgId = site.getOrganizationId();
53
+
54
+ if (handler.enabled) {
55
+ const sites = handler.enabled.sites || [];
56
+ const orgs = handler.enabled.orgs || [];
57
+ return sites.includes(siteId) || orgs.includes(orgId);
58
+ }
59
+
60
+ if (handler.disabled) {
61
+ const sites = handler.disabled.sites || [];
62
+ const orgs = handler.disabled.orgs || [];
63
+ return !(sites.includes(siteId) || orgs.includes(orgId));
64
+ }
65
+
66
+ return handler.enabledByDefault;
67
+ }
68
+
69
+ isHandlerEnabledForOrg(type, org) {
70
+ const handler = this.getHandlers()?.[type];
71
+ if (!handler) return false;
72
+
73
+ const orgId = org.getId();
74
+
75
+ if (handler.enabled) {
76
+ return handler.enabled.orgs?.includes(orgId);
77
+ }
78
+
79
+ if (handler.disabled) {
80
+ return !handler.disabled.orgs?.includes(orgId);
81
+ }
82
+
83
+ return handler.enabledByDefault;
84
+ }
85
+
86
+ #updatedHandler(type, entityId, enabled, entityKey) {
87
+ const handlers = this.getHandlers();
88
+ const handler = handlers?.[type];
89
+
90
+ if (!isNonEmptyObject(handler)) return;
91
+
92
+ if (!isNonEmptyObject(handler.disabled)) {
93
+ handler.disabled = { orgs: [], sites: [] };
94
+ }
95
+
96
+ if (!isNonEmptyObject(handler.enabled)) {
97
+ handler.enabled = { orgs: [], sites: [] };
98
+ }
99
+
100
+ if (enabled) {
101
+ if (handler.enabledByDefault) {
102
+ handler.disabled[entityKey] = handler.disabled[entityKey]
103
+ .filter((id) => id !== entityId) || [];
104
+ } else {
105
+ handler.enabled[entityKey] = Array
106
+ .from(new Set([...(handler.enabled[entityKey] || []), entityId]));
107
+ }
108
+ } else if (handler.enabledByDefault) {
109
+ handler.disabled[entityKey] = Array
110
+ .from(new Set([...(handler.disabled[entityKey] || []), entityId]));
111
+ } else {
112
+ handler.enabled[entityKey] = handler.enabled[entityKey].filter((id) => id !== entityId) || [];
113
+ }
114
+
115
+ handlers[type] = handler;
116
+ this.setHandlers(handlers);
117
+ }
118
+
119
+ updateHandlerOrgs(type, orgId, enabled) {
120
+ this.#updatedHandler(type, orgId, enabled, 'orgs');
121
+ }
122
+
123
+ updateHandlerSites(type, siteId, enabled) {
124
+ this.#updatedHandler(type, siteId, enabled, 'sites');
125
+ }
126
+
127
+ enableHandlerForSite(type, site) {
128
+ const siteId = site.getId();
129
+ if (this.isHandlerEnabledForSite(type, site)) return;
130
+
131
+ this.updateHandlerSites(type, siteId, true);
132
+ }
133
+
134
+ enableHandlerForOrg(type, org) {
135
+ const orgId = org.getId();
136
+ if (this.isHandlerEnabledForOrg(type, org)) return;
137
+
138
+ this.updateHandlerOrgs(type, orgId, true);
139
+ }
140
+
141
+ disableHandlerForSite(type, site) {
142
+ const siteId = site.getId();
143
+ if (!this.isHandlerEnabledForSite(type, site)) return;
144
+
145
+ this.updateHandlerSites(type, siteId, false);
146
+ }
147
+
148
+ disableHandlerForOrg(type, org) {
149
+ const orgId = org.getId();
150
+ if (!this.isHandlerEnabledForOrg(type, org)) return;
151
+
152
+ this.updateHandlerOrgs(type, orgId, false);
153
+ }
154
+
155
+ async save() {
156
+ return this.collection.create(sanitizeIdAndAuditFields(this.constructor.name, this.toJSON()));
157
+ }
158
+ }
159
+
160
+ export default Configuration;
@@ -0,0 +1,103 @@
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
+ /* c8 ignore start */
14
+
15
+ import { isNonEmptyObject } from '@adobe/spacecat-shared-utils';
16
+
17
+ import Joi from 'joi';
18
+
19
+ import SchemaBuilder from '../base/schema.builder.js';
20
+ import Configuration from './configuration.model.js';
21
+ import ConfigurationCollection from './configuration.collection.js';
22
+
23
+ const handlerSchema = Joi.object().pattern(Joi.string(), Joi.object(
24
+ {
25
+ enabled: Joi.object({
26
+ sites: Joi.array().items(Joi.string()),
27
+ orgs: Joi.array().items(Joi.string()),
28
+ }),
29
+ disabled: Joi.object({
30
+ sites: Joi.array().items(Joi.string()),
31
+ orgs: Joi.array().items(Joi.string()),
32
+ }),
33
+ enabledByDefault: Joi.boolean().required(),
34
+ dependencies: Joi.array().items(Joi.object(
35
+ {
36
+ handler: Joi.string(),
37
+ actions: Joi.array().items(Joi.string()),
38
+ },
39
+ )),
40
+ },
41
+ )).unknown(true);
42
+
43
+ const jobsSchema = Joi.array().required();
44
+
45
+ const queueSchema = Joi.object().required();
46
+
47
+ const configurationSchema = Joi.object({
48
+ version: Joi.number().required(),
49
+ queues: queueSchema,
50
+ handlers: handlerSchema,
51
+ jobs: jobsSchema,
52
+ }).unknown(true);
53
+
54
+ export const checkConfiguration = (data, schema = configurationSchema) => {
55
+ const { error, value } = schema.validate(data);
56
+
57
+ if (error) {
58
+ throw new Error(`Configuration validation error: ${error.message}`);
59
+ }
60
+
61
+ return value;
62
+ };
63
+
64
+ /*
65
+ Schema Doc: https://electrodb.dev/en/modeling/schema/
66
+ Attribute Doc: https://electrodb.dev/en/modeling/attributes/
67
+ Indexes Doc: https://electrodb.dev/en/modeling/indexes/
68
+ */
69
+
70
+ const schema = new SchemaBuilder(Configuration, ConfigurationCollection)
71
+ .addAttribute('handlers', {
72
+ type: 'any',
73
+ validate: (value) => !value || checkConfiguration(value, handlerSchema),
74
+ })
75
+ .addAttribute('jobs', {
76
+ type: 'list',
77
+ items: {
78
+ type: 'map',
79
+ properties: {
80
+ group: { type: ['audits', 'imports', 'reports'] },
81
+ type: { type: 'string', required: true },
82
+ interval: { type: ['daily', 'weekly'] },
83
+ },
84
+ },
85
+ })
86
+ .addAttribute('queues', {
87
+ type: 'any',
88
+ required: true,
89
+ validate: (value) => isNonEmptyObject(value),
90
+ })
91
+ .addAttribute('slackRoles', {
92
+ type: 'any',
93
+ validate: (value) => !value || isNonEmptyObject(value),
94
+ })
95
+ .addAttribute('version', {
96
+ type: 'number',
97
+ required: true,
98
+ readOnly: true,
99
+ })
100
+ // eslint-disable-next-line no-template-curly-in-string
101
+ .addAllIndexWithTemplateField('version', '${version}');
102
+
103
+ export default schema.build();
@@ -0,0 +1,111 @@
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 type {
14
+ BaseCollection, BaseModel, Organization, Site,
15
+ } from '../index';
16
+
17
+ export interface Configuration extends BaseModel {
18
+ /**
19
+ * Retrieves the configuration version.
20
+ * @returns {number} The configuration version.
21
+ */
22
+ getVersion: () => number;
23
+
24
+ /**
25
+ * Retrieves the queues configuration.
26
+ * @returns {object} The queues configuration.
27
+ */
28
+ getQueues: () => object;
29
+
30
+ /**
31
+ * Retrieves the jobs configuration.
32
+ * @returns {Array} The jobs configurations.
33
+ */
34
+ getJobs: () => Array<object>;
35
+
36
+ /**
37
+ * Retrieves the handlers configuration.
38
+ * @returns {object} The handlers configuration.
39
+ */
40
+ getHandlers: () => object;
41
+
42
+ /**
43
+ * Retrieves the handler configuration for handler type.
44
+ * @param type The handler type.
45
+ * @returns {object} The handler type configuration.
46
+ */
47
+ getHandler: (type) => object;
48
+
49
+ /**
50
+ * Retrieves the slack roles configuration.
51
+ * @returns {object} The slack roles configuration.
52
+ */
53
+ getSlackRoles: () => object;
54
+
55
+ /**
56
+ * Return true if a handler type is enabled for an organization.
57
+ * @param type handler type
58
+ * @param org organization
59
+ */
60
+ isHandlerEnabledForOrg: (type: string, org: Organization) => boolean;
61
+
62
+ /**
63
+ * Return true if a handler type is enabled for a site.
64
+ * @param type handler type
65
+ * @param site site
66
+ */
67
+ isHandlerEnabledForSite: (type: string, site: Site) => boolean;
68
+
69
+ /**
70
+ * Enables a handler type for an site.
71
+ * @param type handler type
72
+ * @param site site
73
+ */
74
+ enableHandlerForSite: (type: string, site: Site) => void;
75
+
76
+ /**
77
+ * Enables a handler type for an organization.
78
+ * @param type handler type
79
+ * @param org organization
80
+ */
81
+ enableHandlerForOrg: (type: string, org: Organization) => void;
82
+
83
+ /**
84
+ * Disables a handler type for an site.
85
+ * @param type handler type
86
+ * @param site site
87
+ */
88
+ disableHandlerForSite: (type: string, site: Site) => void;
89
+
90
+ /**
91
+ * Disables a handler type for an organization.
92
+ * @param type handler type
93
+ * @param org organization
94
+ */
95
+ disableHandlerForOrg: (type:string, org: Organization) => void;
96
+ }
97
+
98
+ export interface ConfigurationCollection extends BaseCollection<Configuration> {
99
+ /**
100
+ * Retrieves the latest configuration by version.
101
+ * @returns {Configuration} The configuration.
102
+ */
103
+ findLatest: () => Configuration;
104
+
105
+ /**
106
+ * Retrieves the configuration by version.
107
+ * @param version The configuration version.
108
+ * @returns {Configuration} The configuration.
109
+ */
110
+ findByVersion: (version: number) => Configuration;
111
+ }