@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +260 -245
  3. package/docker-compose.test.yml +39 -0
  4. package/package.json +5 -6
  5. package/src/index.js +16 -5
  6. package/src/models/audit/audit.collection.js +6 -20
  7. package/src/models/base/base.collection.js +699 -395
  8. package/src/models/base/base.model.js +13 -6
  9. package/src/models/base/entity.registry.js +25 -5
  10. package/src/models/base/schema.builder.js +2 -0
  11. package/src/models/base/schema.js +15 -4
  12. package/src/models/consumer/consumer.collection.js +149 -0
  13. package/src/models/consumer/consumer.model.js +61 -0
  14. package/src/models/consumer/consumer.schema.js +65 -0
  15. package/src/models/consumer/index.d.ts +39 -0
  16. package/src/models/consumer/index.js +19 -0
  17. package/src/models/fix-entity-suggestion/fix-entity-suggestion.model.js +15 -0
  18. package/src/models/fix-entity-suggestion/fix-entity-suggestion.schema.js +12 -0
  19. package/src/models/index.d.ts +1 -0
  20. package/src/models/index.js +1 -0
  21. package/src/models/key-event/key-event.collection.js +23 -1
  22. package/src/models/latest-audit/latest-audit.collection.js +77 -3
  23. package/src/models/scrape-job/scrape-job.collection.js +38 -0
  24. package/src/models/scrape-job/scrape-job.schema.js +4 -2
  25. package/src/models/site/site.schema.js +1 -0
  26. package/src/models/site-enrollment/site-enrollment.collection.js +11 -1
  27. package/src/models/trial-user/trial-user.collection.js +0 -2
  28. package/src/models/trial-user/trial-user.schema.js +5 -1
  29. package/src/service/index.d.ts +7 -1
  30. package/src/service/index.js +33 -52
  31. package/src/util/index.js +2 -1
  32. package/src/util/logger-registry.js +12 -0
  33. package/src/util/patcher.js +64 -112
  34. package/src/util/postgrest.utils.js +203 -0
  35. 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} electroService - The ElectroDB service used for managing entities.
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(electroService, entityRegistry, schema, record, log) {
61
- this.electroService = electroService;
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.entity, this.schema, this.record);
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
- await this.entity.remove(this.generateCompositeKeys()).go();
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.dynamo - The ElectroDB service instance for DynamoDB operations.
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
- * ElectroDB-based collections are initialized with the dynamo service.
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 ElectroDB-based collections
117
+ // Initialize PostgREST-based collections
112
118
  Object.values(EntityRegistry.entities).forEach(({ collection: Collection, schema }) => {
113
- const collection = new Collection(this.services.dynamo, this, schema, this.log);
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?.facets || []), ...(sk?.facets || [])];
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 = Array.isArray(pk?.facets) ? pk.facets : [];
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 = Array.isArray(index.pk?.facets) ? index.pk.facets : [];
250
- const skKeys = Array.isArray(index.sk?.facets) ? index.sk.facets : [];
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
  })
@@ -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';
@@ -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
- // add custom methods here
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;