@adobe/spacecat-shared-data-access 2.108.0 → 3.0.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 +21 -0
- package/README.md +260 -245
- package/docker-compose.test.yml +39 -0
- package/package.json +5 -6
- package/src/index.js +16 -5
- package/src/models/audit/audit.collection.js +6 -20
- package/src/models/base/base.collection.js +699 -395
- package/src/models/base/base.model.js +13 -6
- package/src/models/base/entity.registry.js +25 -5
- package/src/models/base/schema.builder.js +2 -0
- package/src/models/base/schema.js +15 -4
- package/src/models/consumer/consumer.collection.js +149 -0
- package/src/models/consumer/consumer.model.js +61 -0
- package/src/models/consumer/consumer.schema.js +65 -0
- package/src/models/consumer/index.d.ts +39 -0
- package/src/models/consumer/index.js +19 -0
- package/src/models/fix-entity-suggestion/fix-entity-suggestion.model.js +15 -0
- package/src/models/fix-entity-suggestion/fix-entity-suggestion.schema.js +12 -0
- package/src/models/index.d.ts +1 -0
- package/src/models/index.js +1 -0
- package/src/models/key-event/key-event.collection.js +23 -1
- package/src/models/latest-audit/latest-audit.collection.js +77 -3
- package/src/models/scrape-job/scrape-job.collection.js +38 -0
- package/src/models/scrape-job/scrape-job.schema.js +4 -2
- package/src/models/site/site.schema.js +1 -0
- package/src/models/site-enrollment/site-enrollment.collection.js +11 -1
- package/src/models/trial-user/trial-user.collection.js +0 -2
- package/src/models/trial-user/trial-user.schema.js +5 -1
- package/src/service/index.d.ts +7 -1
- package/src/service/index.js +33 -52
- package/src/util/index.js +2 -1
- package/src/util/logger-registry.js +12 -0
- package/src/util/patcher.js +64 -112
- package/src/util/postgrest.utils.js +203 -0
- package/tsconfig.json +24 -0
|
@@ -50,15 +50,15 @@ class BaseModel {
|
|
|
50
50
|
/**
|
|
51
51
|
* Constructs an instance of BaseModel.
|
|
52
52
|
* @constructor
|
|
53
|
-
* @param {Object}
|
|
53
|
+
* @param {Object} postgrestService - The PostgREST client used for managing entities.
|
|
54
54
|
* @param {EntityRegistry} entityRegistry - The registry holding entities, their schema
|
|
55
55
|
* and collection.
|
|
56
56
|
* @param {Schema} schema - The schema for the entity.
|
|
57
57
|
* @param {Object} record - The initial data for the entity instance.
|
|
58
58
|
* @param {Object} log - A log for capturing logging information.
|
|
59
59
|
*/
|
|
60
|
-
constructor(
|
|
61
|
-
this.
|
|
60
|
+
constructor(postgrestService, entityRegistry, schema, record, log) {
|
|
61
|
+
this.postgrestService = postgrestService;
|
|
62
62
|
this.entityRegistry = entityRegistry;
|
|
63
63
|
this.schema = schema;
|
|
64
64
|
this.record = record;
|
|
@@ -68,9 +68,8 @@ class BaseModel {
|
|
|
68
68
|
this.idName = entityNameToIdName(this.entityName);
|
|
69
69
|
|
|
70
70
|
this.collection = entityRegistry.getCollection(schema.getCollectionName());
|
|
71
|
-
this.entity = electroService.entities[this.entityName];
|
|
72
71
|
|
|
73
|
-
this.patcher = new Patcher(this.
|
|
72
|
+
this.patcher = new Patcher(this.collection, this.schema, this.record);
|
|
74
73
|
|
|
75
74
|
this._accessorCache = {};
|
|
76
75
|
|
|
@@ -283,7 +282,15 @@ class BaseModel {
|
|
|
283
282
|
|
|
284
283
|
await Promise.all(removePromises);
|
|
285
284
|
|
|
286
|
-
|
|
285
|
+
if (this.collection && typeof this.collection.removeByIndexKeys === 'function') {
|
|
286
|
+
await this.collection.removeByIndexKeys([this.generateCompositeKeys()]);
|
|
287
|
+
} else if (this.postgrestService?.entities?.[this.entityName]?.remove) {
|
|
288
|
+
await this.postgrestService.entities[this.entityName]
|
|
289
|
+
.remove(this.generateCompositeKeys())
|
|
290
|
+
.go();
|
|
291
|
+
} else {
|
|
292
|
+
throw new DataAccessError(`No remove strategy available for ${this.entityName}`, this);
|
|
293
|
+
}
|
|
287
294
|
|
|
288
295
|
this.#invalidateCache();
|
|
289
296
|
|
|
@@ -18,6 +18,7 @@ import AsyncJobCollection from '../async-job/async-job.collection.js';
|
|
|
18
18
|
import AuditCollection from '../audit/audit.collection.js';
|
|
19
19
|
import AuditUrlCollection from '../audit-url/audit-url.collection.js';
|
|
20
20
|
import ConfigurationCollection from '../configuration/configuration.collection.js';
|
|
21
|
+
import ConsumerCollection from '../consumer/consumer.collection.js';
|
|
21
22
|
import ExperimentCollection from '../experiment/experiment.collection.js';
|
|
22
23
|
import EntitlementCollection from '../entitlement/entitlement.collection.js';
|
|
23
24
|
import FixEntityCollection from '../fix-entity/fix-entity.collection.js';
|
|
@@ -49,6 +50,7 @@ import ApiKeySchema from '../api-key/api-key.schema.js';
|
|
|
49
50
|
import AsyncJobSchema from '../async-job/async-job.schema.js';
|
|
50
51
|
import AuditSchema from '../audit/audit.schema.js';
|
|
51
52
|
import AuditUrlSchema from '../audit-url/audit-url.schema.js';
|
|
53
|
+
import ConsumerSchema from '../consumer/consumer.schema.js';
|
|
52
54
|
import EntitlementSchema from '../entitlement/entitlement.schema.js';
|
|
53
55
|
import FixEntitySchema from '../fix-entity/fix-entity.schema.js';
|
|
54
56
|
import FixEntitySuggestionSchema from '../fix-entity-suggestion/fix-entity-suggestion.schema.js';
|
|
@@ -84,16 +86,20 @@ import SentimentTopicSchema from '../sentiment-topic/sentiment-topic.schema.js';
|
|
|
84
86
|
class EntityRegistry {
|
|
85
87
|
static entities = {};
|
|
86
88
|
|
|
89
|
+
static defaultEntities = {};
|
|
90
|
+
|
|
87
91
|
/**
|
|
88
92
|
* Constructs an instance of EntityRegistry.
|
|
89
93
|
* @constructor
|
|
90
94
|
* @param {Object} services - Dictionary of services keyed by datastore type.
|
|
91
|
-
* @param {Object} services.
|
|
95
|
+
* @param {Object} services.postgrest - The PostgREST client instance for Postgres operations.
|
|
92
96
|
* @param {{s3Client: S3Client, s3Bucket: string}|null} [services.s3] - S3 service configuration.
|
|
97
|
+
* @param {Object} config - Configuration object containing environment-derived settings.
|
|
93
98
|
* @param {Object} log - A logger for capturing and logging information.
|
|
94
99
|
*/
|
|
95
|
-
constructor(services, log) {
|
|
100
|
+
constructor(services, config, log) {
|
|
96
101
|
this.services = services;
|
|
102
|
+
this.config = config;
|
|
97
103
|
this.log = log;
|
|
98
104
|
this.collections = new Map();
|
|
99
105
|
|
|
@@ -103,14 +109,14 @@ class EntityRegistry {
|
|
|
103
109
|
/**
|
|
104
110
|
* Initializes the collections managed by the EntityRegistry.
|
|
105
111
|
* This method creates instances of each collection and stores them in an internal map.
|
|
106
|
-
*
|
|
112
|
+
* PostgREST-based collections are initialized with the postgrest service.
|
|
107
113
|
* Configuration is handled specially as it's a standalone S3-based collection.
|
|
108
114
|
* @private
|
|
109
115
|
*/
|
|
110
116
|
#initialize() {
|
|
111
|
-
// Initialize
|
|
117
|
+
// Initialize PostgREST-based collections
|
|
112
118
|
Object.values(EntityRegistry.entities).forEach(({ collection: Collection, schema }) => {
|
|
113
|
-
const collection = new Collection(this.services.
|
|
119
|
+
const collection = new Collection(this.services.postgrest, this, schema, this.log);
|
|
114
120
|
this.collections.set(Collection.COLLECTION_NAME, collection);
|
|
115
121
|
});
|
|
116
122
|
|
|
@@ -142,6 +148,14 @@ class EntityRegistry {
|
|
|
142
148
|
return collections;
|
|
143
149
|
}
|
|
144
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Returns the camelCase names of all registered entities (including Configuration).
|
|
153
|
+
* @returns {string[]} - An array of entity names.
|
|
154
|
+
*/
|
|
155
|
+
getEntityNames() {
|
|
156
|
+
return [...Object.keys(this.constructor.entities), 'configuration'];
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
static getEntities() {
|
|
146
160
|
return Object.keys(this.entities).reduce((acc, key) => {
|
|
147
161
|
acc[key] = this.entities[key].schema.toElectroDBSchema();
|
|
@@ -152,6 +166,10 @@ class EntityRegistry {
|
|
|
152
166
|
static registerEntity(schema, collection) {
|
|
153
167
|
this.entities[decapitalize(schema.getEntityName())] = { schema, collection };
|
|
154
168
|
}
|
|
169
|
+
|
|
170
|
+
static resetEntities() {
|
|
171
|
+
this.entities = { ...this.defaultEntities };
|
|
172
|
+
}
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
// Register ElectroDB-based entities only (Configuration is handled separately)
|
|
@@ -159,6 +177,7 @@ EntityRegistry.registerEntity(ApiKeySchema, ApiKeyCollection);
|
|
|
159
177
|
EntityRegistry.registerEntity(AsyncJobSchema, AsyncJobCollection);
|
|
160
178
|
EntityRegistry.registerEntity(AuditSchema, AuditCollection);
|
|
161
179
|
EntityRegistry.registerEntity(AuditUrlSchema, AuditUrlCollection);
|
|
180
|
+
EntityRegistry.registerEntity(ConsumerSchema, ConsumerCollection);
|
|
162
181
|
EntityRegistry.registerEntity(EntitlementSchema, EntitlementCollection);
|
|
163
182
|
EntityRegistry.registerEntity(FixEntitySchema, FixEntityCollection);
|
|
164
183
|
EntityRegistry.registerEntity(FixEntitySuggestionSchema, FixEntitySuggestionCollection);
|
|
@@ -185,5 +204,6 @@ EntityRegistry.registerEntity(TrialUserActivitySchema, TrialUserActivityCollecti
|
|
|
185
204
|
EntityRegistry.registerEntity(PageCitabilitySchema, PageCitabilityCollection);
|
|
186
205
|
EntityRegistry.registerEntity(SentimentGuidelineSchema, SentimentGuidelineCollection);
|
|
187
206
|
EntityRegistry.registerEntity(SentimentTopicSchema, SentimentTopicCollection);
|
|
207
|
+
EntityRegistry.defaultEntities = { ...EntityRegistry.entities };
|
|
188
208
|
|
|
189
209
|
export default EntityRegistry;
|
|
@@ -36,6 +36,7 @@ const DEFAULT_SERVICE_NAME = 'SpaceCat';
|
|
|
36
36
|
*/
|
|
37
37
|
const ID_ATTRIBUTE_DATA = {
|
|
38
38
|
type: 'string',
|
|
39
|
+
postgrestField: 'id',
|
|
39
40
|
required: true,
|
|
40
41
|
readOnly: true,
|
|
41
42
|
// https://electrodb.dev/en/modeling/attributes/#default
|
|
@@ -215,6 +216,7 @@ class SchemaBuilder {
|
|
|
215
216
|
|
|
216
217
|
this.addAttribute('recordExpiresAt', {
|
|
217
218
|
type: 'number',
|
|
219
|
+
postgrestIgnore: true,
|
|
218
220
|
required: true,
|
|
219
221
|
readOnly: true,
|
|
220
222
|
default: () => Math.floor(Date.now() / 1000) + ttlInDays * 24 * 60 * 60,
|
|
@@ -156,6 +156,17 @@ class Schema {
|
|
|
156
156
|
return result;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Internal helper: v3 schemas use `composite` keys (legacy Electro used `facets`).
|
|
160
|
+
static #getKeyParts(indexPart) {
|
|
161
|
+
if (Array.isArray(indexPart?.composite)) {
|
|
162
|
+
return indexPart.composite;
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(indexPart?.facets)) {
|
|
165
|
+
return indexPart.facets;
|
|
166
|
+
}
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
159
170
|
getIndexByName(indexName) {
|
|
160
171
|
return this.indexes[indexName];
|
|
161
172
|
}
|
|
@@ -167,10 +178,10 @@ class Schema {
|
|
|
167
178
|
const subKeyNames = sortKeys.slice(0, length);
|
|
168
179
|
const index = Object.values(this.indexes).find((candidate) => {
|
|
169
180
|
const { pk, sk } = candidate;
|
|
170
|
-
const allKeys = [...(pk
|
|
181
|
+
const allKeys = [...Schema.#getKeyParts(pk), ...Schema.#getKeyParts(sk)];
|
|
171
182
|
|
|
172
183
|
// check if all keys in the index are in the sort keys
|
|
173
|
-
const pkKeys =
|
|
184
|
+
const pkKeys = Schema.#getKeyParts(pk);
|
|
174
185
|
return pkKeys.every((key) => subKeyNames.includes(key))
|
|
175
186
|
&& subKeyNames.every((key) => allKeys.includes(key));
|
|
176
187
|
});
|
|
@@ -246,8 +257,8 @@ class Schema {
|
|
|
246
257
|
return [];
|
|
247
258
|
}
|
|
248
259
|
|
|
249
|
-
const pkKeys =
|
|
250
|
-
const skKeys =
|
|
260
|
+
const pkKeys = Schema.#getKeyParts(index.pk);
|
|
261
|
+
const skKeys = Schema.#getKeyParts(index.sk);
|
|
251
262
|
|
|
252
263
|
return [...pkKeys, ...skKeys];
|
|
253
264
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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, isNonEmptyArray } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import BaseCollection from '../base/base.collection.js';
|
|
16
|
+
import ValidationError from '../../errors/validation.error.js';
|
|
17
|
+
import Consumer from './consumer.model.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ConsumerCollection - A collection class responsible for managing Consumer entities.
|
|
21
|
+
* Extends the BaseCollection to provide specific methods for interacting with Consumer records.
|
|
22
|
+
*
|
|
23
|
+
* @class ConsumerCollection
|
|
24
|
+
* @extends BaseCollection
|
|
25
|
+
*/
|
|
26
|
+
class ConsumerCollection extends BaseCollection {
|
|
27
|
+
static COLLECTION_NAME = 'ConsumerCollection';
|
|
28
|
+
|
|
29
|
+
#validCapabilities = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the set of valid capabilities, generated dynamically from all registered
|
|
33
|
+
* entity names and operations. Computed once and cached.
|
|
34
|
+
* Format: entityName:operation (e.g. "site:read", "organization:write")
|
|
35
|
+
* @returns {Set<string>} - The set of valid capability strings.
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
#getValidCapabilities() {
|
|
39
|
+
if (!this.#validCapabilities) {
|
|
40
|
+
const entityNames = this.entityRegistry.getEntityNames();
|
|
41
|
+
this.#validCapabilities = new Set(
|
|
42
|
+
entityNames.flatMap(
|
|
43
|
+
(name) => Consumer.CAPABILITIES.map((op) => `${name}:${op}`),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return this.#validCapabilities;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Validates that all capabilities in the given array are known entity:operation pairs.
|
|
52
|
+
* Called during both create() and save() to prevent capability escalation.
|
|
53
|
+
* @param {string[]} capabilities - The capabilities to validate.
|
|
54
|
+
* @throws {ValidationError} - Throws if any capability is not recognized.
|
|
55
|
+
*/
|
|
56
|
+
validateCapabilities(capabilities) {
|
|
57
|
+
if (!isNonEmptyArray(capabilities)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const validCapabilities = this.#getValidCapabilities();
|
|
62
|
+
const invalid = capabilities.filter((cap) => !validCapabilities.has(cap));
|
|
63
|
+
|
|
64
|
+
if (invalid.length > 0) {
|
|
65
|
+
throw new ValidationError(
|
|
66
|
+
`Invalid capabilities: [${invalid.join(', ')}]`,
|
|
67
|
+
this,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validates that the given imsOrgId is in the allowed list from config.
|
|
74
|
+
* @param {string} imsOrgId - The IMS Org ID to validate.
|
|
75
|
+
* @throws {ValidationError} - Throws if the imsOrgId is not in the allowed list.
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
#validateImsOrgId(imsOrgId) {
|
|
79
|
+
const { s2sAllowedImsOrgIds } = this.entityRegistry.config;
|
|
80
|
+
|
|
81
|
+
if (!isNonEmptyArray(s2sAllowedImsOrgIds)) {
|
|
82
|
+
throw new ValidationError(
|
|
83
|
+
'S2S_ALLOWED_IMS_ORG_IDS is not configured. Cannot create a consumer without an allowlist.',
|
|
84
|
+
this,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!s2sAllowedImsOrgIds.includes(imsOrgId)) {
|
|
89
|
+
throw new ValidationError(
|
|
90
|
+
`The imsOrgId "${imsOrgId}" is not in the list of allowed IMS Org IDs`,
|
|
91
|
+
this,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validates that no existing consumer has the same clientId.
|
|
98
|
+
* Uses findByClientId() to enforce global uniqueness, which is correct because IMS
|
|
99
|
+
* client_ids are globally unique per integration in Adobe Developer Console regardless
|
|
100
|
+
* of the owning org. The composite index ['clientId', 'imsOrgId'] exists for efficient
|
|
101
|
+
* scoped lookups, not to imply per-org uniqueness.
|
|
102
|
+
* @param {string} clientId - The clientId to check.
|
|
103
|
+
* @throws {ValidationError} - Throws if a consumer with this clientId already exists.
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
async #validateClientIdUniqueness(clientId) {
|
|
107
|
+
if (!hasText(clientId)) {
|
|
108
|
+
throw new ValidationError(
|
|
109
|
+
'clientId is required to create a consumer',
|
|
110
|
+
this,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const existing = await this.findByClientId(clientId);
|
|
115
|
+
if (existing) {
|
|
116
|
+
throw new ValidationError(
|
|
117
|
+
`A consumer with clientId "${clientId}" already exists`,
|
|
118
|
+
this,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new Consumer entity after validating imsOrgId, capabilities, and clientId uniqueness.
|
|
125
|
+
* @param {Object} item - The data for the entity to be created.
|
|
126
|
+
* @param {Object} [options] - Additional options for the creation process.
|
|
127
|
+
* @returns {Promise<BaseModel>} - A promise that resolves to the created model instance.
|
|
128
|
+
* @throws {ValidationError} - Throws if validation fails.
|
|
129
|
+
*/
|
|
130
|
+
async create(item, options = {}) {
|
|
131
|
+
this.#validateImsOrgId(item?.imsOrgId);
|
|
132
|
+
this.validateCapabilities(item?.capabilities);
|
|
133
|
+
await this.#validateClientIdUniqueness(item?.clientId);
|
|
134
|
+
return super.create(item, options);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Bulk creation is not supported for Consumer entities.
|
|
139
|
+
* All consumers must be created individually via create() to enforce
|
|
140
|
+
* imsOrgId allowlist, capability validation, and clientId uniqueness checks.
|
|
141
|
+
* @throws {Error} Always throws — use create() instead.
|
|
142
|
+
*/
|
|
143
|
+
// eslint-disable-next-line class-methods-use-this, no-unused-vars
|
|
144
|
+
async createMany(_items, _parent) {
|
|
145
|
+
throw new Error('createMany is not supported for Consumer. Use create() instead.');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default ConsumerCollection;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 { isIsoDate } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import BaseModel from '../base/base.model.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Consumer - A class representing a Consumer entity.
|
|
19
|
+
* Provides methods to access and manipulate Consumer-specific data.
|
|
20
|
+
*
|
|
21
|
+
* @class Consumer
|
|
22
|
+
* @extends BaseModel
|
|
23
|
+
*/
|
|
24
|
+
class Consumer extends BaseModel {
|
|
25
|
+
static ENTITY_NAME = 'Consumer';
|
|
26
|
+
|
|
27
|
+
static STATUS = {
|
|
28
|
+
ACTIVE: 'ACTIVE',
|
|
29
|
+
SUSPENDED: 'SUSPENDED',
|
|
30
|
+
REVOKED: 'REVOKED',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Checks whether this consumer has been revoked.
|
|
35
|
+
* @returns {boolean} - True if the consumer has been revoked.
|
|
36
|
+
*/
|
|
37
|
+
isRevoked() {
|
|
38
|
+
return this.getStatus() === Consumer.STATUS.REVOKED
|
|
39
|
+
|| (isIsoDate(this.getRevokedAt()) && new Date(this.getRevokedAt()) <= new Date());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Saves the consumer after validating capabilities against the allowlist.
|
|
44
|
+
* Prevents capability escalation via setCapabilities() + save().
|
|
45
|
+
* @async
|
|
46
|
+
* @returns {Promise<Consumer>} - The saved consumer instance.
|
|
47
|
+
* @throws {ValidationError} - If capabilities are invalid.
|
|
48
|
+
*/
|
|
49
|
+
async save() {
|
|
50
|
+
this.collection.validateCapabilities(this.getCapabilities());
|
|
51
|
+
return super.save();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static CAPABILITIES = ['read', 'write', 'delete'];
|
|
55
|
+
|
|
56
|
+
static IMS_ORG_ID_REGEX = /^[a-z0-9]{24}@AdobeOrg$/i;
|
|
57
|
+
|
|
58
|
+
static TECHNICAL_ACCOUNT_ID_REGEX = /^[a-z0-9]{24}@techacct\.adobe\.com$/i;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default Consumer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 { isIsoDate } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
16
|
+
import Consumer from './consumer.model.js';
|
|
17
|
+
import ConsumerCollection from './consumer.collection.js';
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
Schema Doc: https://electrodb.dev/en/modeling/schema/
|
|
21
|
+
Attribute Doc: https://electrodb.dev/en/modeling/attributes/
|
|
22
|
+
Indexes Doc: https://electrodb.dev/en/modeling/indexes/
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const schema = new SchemaBuilder(Consumer, ConsumerCollection)
|
|
26
|
+
.addAttribute('clientId', {
|
|
27
|
+
type: 'string',
|
|
28
|
+
required: true,
|
|
29
|
+
readOnly: true,
|
|
30
|
+
})
|
|
31
|
+
.addAttribute('technicalAccountId', {
|
|
32
|
+
type: 'string',
|
|
33
|
+
required: true,
|
|
34
|
+
readOnly: true,
|
|
35
|
+
validate: (value) => Consumer.TECHNICAL_ACCOUNT_ID_REGEX.test(value),
|
|
36
|
+
})
|
|
37
|
+
.addAttribute('consumerName', {
|
|
38
|
+
type: 'string',
|
|
39
|
+
required: true,
|
|
40
|
+
})
|
|
41
|
+
.addAttribute('status', {
|
|
42
|
+
type: Object.values(Consumer.STATUS),
|
|
43
|
+
required: true,
|
|
44
|
+
})
|
|
45
|
+
.addAttribute('capabilities', {
|
|
46
|
+
type: 'list',
|
|
47
|
+
required: true,
|
|
48
|
+
items: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
.addAttribute('revokedAt', {
|
|
53
|
+
type: 'string',
|
|
54
|
+
validate: (value) => !value || isIsoDate(value),
|
|
55
|
+
})
|
|
56
|
+
.addAttribute('imsOrgId', {
|
|
57
|
+
type: 'string',
|
|
58
|
+
required: true,
|
|
59
|
+
readOnly: true,
|
|
60
|
+
validate: (value) => Consumer.IMS_ORG_ID_REGEX.test(value),
|
|
61
|
+
})
|
|
62
|
+
.addAllIndex(['imsOrgId'])
|
|
63
|
+
.addAllIndex(['clientId', 'imsOrgId']);
|
|
64
|
+
|
|
65
|
+
export default schema.build();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 { BaseCollection, BaseModel } from '../index';
|
|
14
|
+
|
|
15
|
+
export type ConsumerStatus = 'ACTIVE' | 'SUSPENDED' | 'REVOKED';
|
|
16
|
+
|
|
17
|
+
export interface Consumer extends BaseModel {
|
|
18
|
+
getClientId(): string;
|
|
19
|
+
getTechnicalAccountId(): string;
|
|
20
|
+
getConsumerName(): string;
|
|
21
|
+
getStatus(): ConsumerStatus;
|
|
22
|
+
getCapabilities(): string[];
|
|
23
|
+
getImsOrgId(): string;
|
|
24
|
+
getRevokedAt(): string | undefined;
|
|
25
|
+
isRevoked(): boolean;
|
|
26
|
+
setConsumerName(consumerName: string): Consumer;
|
|
27
|
+
setStatus(status: ConsumerStatus): Consumer;
|
|
28
|
+
setCapabilities(capabilities: string[]): Consumer;
|
|
29
|
+
setRevokedAt(revokedAt: string): Consumer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ConsumerCollection extends BaseCollection<Consumer> {
|
|
33
|
+
allByImsOrgId(imsOrgId: string): Promise<Consumer[]>;
|
|
34
|
+
allByClientId(clientId: string): Promise<Consumer[]>;
|
|
35
|
+
allByClientIdAndImsOrgId(clientId: string, imsOrgId: string): Promise<Consumer[]>;
|
|
36
|
+
findByImsOrgId(imsOrgId: string): Promise<Consumer | null>;
|
|
37
|
+
findByClientId(clientId: string): Promise<Consumer | null>;
|
|
38
|
+
findByClientIdAndImsOrgId(clientId: string, imsOrgId: string): Promise<Consumer | null>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 Consumer from './consumer.model.js';
|
|
14
|
+
import ConsumerCollection from './consumer.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
Consumer,
|
|
18
|
+
ConsumerCollection,
|
|
19
|
+
};
|
|
@@ -25,6 +25,21 @@ class FixEntitySuggestion extends BaseModel {
|
|
|
25
25
|
|
|
26
26
|
static DEFAULT_UPDATED_BY = 'spacecat';
|
|
27
27
|
|
|
28
|
+
getId() {
|
|
29
|
+
const id = super.getId();
|
|
30
|
+
if (id) {
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const suggestionId = this.getSuggestionId();
|
|
35
|
+
const fixEntityId = this.getFixEntityId();
|
|
36
|
+
if (suggestionId && fixEntityId) {
|
|
37
|
+
return `${suggestionId}#${fixEntityId}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
/**
|
|
29
44
|
* Generates the composite keys for the FixEntitySuggestion model.
|
|
30
45
|
* @returns {Object} - The composite keys.
|
|
@@ -23,6 +23,17 @@ Indexes Doc: https://electrodb.dev/en/modeling/indexes/
|
|
|
23
23
|
const schema = new SchemaBuilder(FixEntitySuggestion, FixEntitySuggestionCollection)
|
|
24
24
|
.withPrimaryPartitionKeys(['suggestionId'])
|
|
25
25
|
.withPrimarySortKeys(['fixEntityId'])
|
|
26
|
+
.addAttribute('fixEntitySuggestionId', {
|
|
27
|
+
type: 'string',
|
|
28
|
+
required: false,
|
|
29
|
+
readOnly: true,
|
|
30
|
+
postgrestIgnore: true,
|
|
31
|
+
})
|
|
32
|
+
.addAttribute('updatedBy', {
|
|
33
|
+
type: 'string',
|
|
34
|
+
required: false,
|
|
35
|
+
postgrestIgnore: true,
|
|
36
|
+
})
|
|
26
37
|
.addReference('belongs_to', 'FixEntity')
|
|
27
38
|
.addReference('belongs_to', 'Suggestion')
|
|
28
39
|
.addAttribute('opportunityId', {
|
|
@@ -38,6 +49,7 @@ const schema = new SchemaBuilder(FixEntitySuggestion, FixEntitySuggestionCollect
|
|
|
38
49
|
.addAttribute('fixEntityCreatedDate', {
|
|
39
50
|
type: 'string',
|
|
40
51
|
readOnly: true,
|
|
52
|
+
postgrestIgnore: true,
|
|
41
53
|
watch: ['fixEntityCreatedAt'],
|
|
42
54
|
set: (_, { fixEntityCreatedAt }) => (fixEntityCreatedAt ? fixEntityCreatedAt.split('T')[0] : undefined),
|
|
43
55
|
})
|
package/src/models/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type * from './audit';
|
|
|
14
14
|
export type * from './async-job';
|
|
15
15
|
export type * from './configuration';
|
|
16
16
|
export type * from './base';
|
|
17
|
+
export type * from './consumer';
|
|
17
18
|
export type * from './fix-entity';
|
|
18
19
|
export type * from './fix-entity-suggestion';
|
|
19
20
|
export type * from './experiment';
|
package/src/models/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from './audit/index.js';
|
|
|
16
16
|
export * from './audit-url/index.js';
|
|
17
17
|
export * from './base/index.js';
|
|
18
18
|
export * from './configuration/index.js';
|
|
19
|
+
export * from './consumer/index.js';
|
|
19
20
|
export * from './entitlement/index.js';
|
|
20
21
|
export * from './fix-entity/index.js';
|
|
21
22
|
export * from './fix-entity-suggestion/index.js';
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import BaseCollection from '../base/base.collection.js';
|
|
14
|
+
import DataAccessError from '../../errors/data-access.error.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* KeyEventCollection - A collection class responsible for managing KeyEvent entities.
|
|
@@ -22,7 +23,28 @@ import BaseCollection from '../base/base.collection.js';
|
|
|
22
23
|
class KeyEventCollection extends BaseCollection {
|
|
23
24
|
static COLLECTION_NAME = 'KeyEventCollection';
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
#throwDeprecated() {
|
|
27
|
+
throw new DataAccessError(
|
|
28
|
+
'KeyEvent is deprecated in data-access v3. Use Audit/LatestAudit and related Postgres-backed entities instead.',
|
|
29
|
+
this,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async all() { return this.#throwDeprecated(); }
|
|
34
|
+
|
|
35
|
+
async allByIndexKeys() { return this.#throwDeprecated(); }
|
|
36
|
+
|
|
37
|
+
async findById() { return this.#throwDeprecated(); }
|
|
38
|
+
|
|
39
|
+
async findByIndexKeys() { return this.#throwDeprecated(); }
|
|
40
|
+
|
|
41
|
+
async create() { return this.#throwDeprecated(); }
|
|
42
|
+
|
|
43
|
+
async createMany() { return this.#throwDeprecated(); }
|
|
44
|
+
|
|
45
|
+
async removeByIds() { return this.#throwDeprecated(); }
|
|
46
|
+
|
|
47
|
+
async removeByIndexKeys() { return this.#throwDeprecated(); }
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
export default KeyEventCollection;
|