@adobe/spacecat-shared-data-access 2.109.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 +14 -0
- package/README.md +260 -245
- package/docker-compose.test.yml +39 -0
- package/package.json +5 -6
- package/src/index.js +10 -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 +11 -4
- package/src/models/base/schema.builder.js +2 -0
- package/src/models/base/schema.js +15 -4
- 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/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 +32 -51
- 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
|
|
|
@@ -86,11 +86,13 @@ import SentimentTopicSchema from '../sentiment-topic/sentiment-topic.schema.js';
|
|
|
86
86
|
class EntityRegistry {
|
|
87
87
|
static entities = {};
|
|
88
88
|
|
|
89
|
+
static defaultEntities = {};
|
|
90
|
+
|
|
89
91
|
/**
|
|
90
92
|
* Constructs an instance of EntityRegistry.
|
|
91
93
|
* @constructor
|
|
92
94
|
* @param {Object} services - Dictionary of services keyed by datastore type.
|
|
93
|
-
* @param {Object} services.
|
|
95
|
+
* @param {Object} services.postgrest - The PostgREST client instance for Postgres operations.
|
|
94
96
|
* @param {{s3Client: S3Client, s3Bucket: string}|null} [services.s3] - S3 service configuration.
|
|
95
97
|
* @param {Object} config - Configuration object containing environment-derived settings.
|
|
96
98
|
* @param {Object} log - A logger for capturing and logging information.
|
|
@@ -107,14 +109,14 @@ class EntityRegistry {
|
|
|
107
109
|
/**
|
|
108
110
|
* Initializes the collections managed by the EntityRegistry.
|
|
109
111
|
* This method creates instances of each collection and stores them in an internal map.
|
|
110
|
-
*
|
|
112
|
+
* PostgREST-based collections are initialized with the postgrest service.
|
|
111
113
|
* Configuration is handled specially as it's a standalone S3-based collection.
|
|
112
114
|
* @private
|
|
113
115
|
*/
|
|
114
116
|
#initialize() {
|
|
115
|
-
// Initialize
|
|
117
|
+
// Initialize PostgREST-based collections
|
|
116
118
|
Object.values(EntityRegistry.entities).forEach(({ collection: Collection, schema }) => {
|
|
117
|
-
const collection = new Collection(this.services.
|
|
119
|
+
const collection = new Collection(this.services.postgrest, this, schema, this.log);
|
|
118
120
|
this.collections.set(Collection.COLLECTION_NAME, collection);
|
|
119
121
|
});
|
|
120
122
|
|
|
@@ -164,6 +166,10 @@ class EntityRegistry {
|
|
|
164
166
|
static registerEntity(schema, collection) {
|
|
165
167
|
this.entities[decapitalize(schema.getEntityName())] = { schema, collection };
|
|
166
168
|
}
|
|
169
|
+
|
|
170
|
+
static resetEntities() {
|
|
171
|
+
this.entities = { ...this.defaultEntities };
|
|
172
|
+
}
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
// Register ElectroDB-based entities only (Configuration is handled separately)
|
|
@@ -198,5 +204,6 @@ EntityRegistry.registerEntity(TrialUserActivitySchema, TrialUserActivityCollecti
|
|
|
198
204
|
EntityRegistry.registerEntity(PageCitabilitySchema, PageCitabilityCollection);
|
|
199
205
|
EntityRegistry.registerEntity(SentimentGuidelineSchema, SentimentGuidelineCollection);
|
|
200
206
|
EntityRegistry.registerEntity(SentimentTopicSchema, SentimentTopicCollection);
|
|
207
|
+
EntityRegistry.defaultEntities = { ...EntityRegistry.entities };
|
|
201
208
|
|
|
202
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
|
}
|
|
@@ -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
|
})
|
|
@@ -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;
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
import DataAccessError from '../../errors/data-access.error.js';
|
|
14
15
|
import { guardId, guardString } from '../../util/index.js';
|
|
16
|
+
import BaseCollection from '../base/base.collection.js';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* LatestAuditCollection - A collection class responsible for managing LatestAudit entities.
|
|
@@ -23,8 +25,80 @@ import { guardId, guardString } from '../../util/index.js';
|
|
|
23
25
|
class LatestAuditCollection extends BaseCollection {
|
|
24
26
|
static COLLECTION_NAME = 'LatestAuditCollection';
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// LatestAudit is a virtual view in v3; writes are not supported.
|
|
29
|
+
// eslint-disable-next-line class-methods-use-this
|
|
30
|
+
async create() {
|
|
31
|
+
throw new DataAccessError('LatestAudit is derived from Audit in v3 and cannot be created directly', this);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// eslint-disable-next-line class-methods-use-this
|
|
35
|
+
async createMany() {
|
|
36
|
+
throw new DataAccessError('LatestAudit is derived from Audit in v3 and cannot be created directly', this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static #groupLatest(items, groupFields) {
|
|
40
|
+
const grouped = new Map();
|
|
41
|
+
items.forEach((item) => {
|
|
42
|
+
const key = groupFields.map((field) => item.record[field]).join('#');
|
|
43
|
+
const existing = grouped.get(key);
|
|
44
|
+
if (!existing || existing.getAuditedAt() < item.getAuditedAt()) {
|
|
45
|
+
grouped.set(key, item);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return [...grouped.values()];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async #allAuditsByKeys(keys, options = {}) {
|
|
52
|
+
const auditCollection = this.entityRegistry.getCollection('AuditCollection');
|
|
53
|
+
return auditCollection.allByIndexKeys(keys, {
|
|
54
|
+
...options,
|
|
55
|
+
fetchAllPages: true,
|
|
56
|
+
order: 'desc',
|
|
57
|
+
returnCursor: false,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async all(sortKeys = {}, options = {}) {
|
|
62
|
+
return this.allByIndexKeys(sortKeys, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async findByAll(sortKeys = {}, options = {}) {
|
|
66
|
+
return this.findByIndexKeys(sortKeys, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async findByIndexKeys(keys, options = {}) {
|
|
70
|
+
const auditCollection = this.entityRegistry.getCollection('AuditCollection');
|
|
71
|
+
|
|
72
|
+
if (keys.siteId && keys.auditType) {
|
|
73
|
+
return auditCollection.findByIndexKeys(keys, { ...options, order: 'desc' });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// For single-key lookups, we only need the latest row, not all pages.
|
|
77
|
+
return auditCollection.findByIndexKeys(keys, { ...options, order: 'desc' });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async allByIndexKeys(keys, options = {}) {
|
|
81
|
+
const audits = await this.#allAuditsByKeys(keys, options);
|
|
82
|
+
if (!isNonEmptyArray(audits)) {
|
|
83
|
+
return options.returnCursor ? { data: [], cursor: null } : [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let groupFields = ['siteId', 'auditType'];
|
|
87
|
+
if (keys.siteId && !keys.auditType) {
|
|
88
|
+
groupFields = ['auditType'];
|
|
89
|
+
} else if (!keys.siteId && keys.auditType) {
|
|
90
|
+
groupFields = ['siteId'];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const latest = LatestAuditCollection.#groupLatest(audits, groupFields);
|
|
94
|
+
// Preserve v2 behavior: default order is descending (most recent first).
|
|
95
|
+
const ascending = options.order === 'asc';
|
|
96
|
+
latest.sort((a, b) => (ascending
|
|
97
|
+
? a.getAuditedAt().localeCompare(b.getAuditedAt())
|
|
98
|
+
: b.getAuditedAt().localeCompare(a.getAuditedAt())));
|
|
99
|
+
const limited = Number.isInteger(options.limit) ? latest.slice(0, options.limit) : latest;
|
|
100
|
+
|
|
101
|
+
return options.returnCursor ? { data: limited, cursor: null } : limited;
|
|
28
102
|
}
|
|
29
103
|
|
|
30
104
|
async allByAuditType(auditType) {
|
|
@@ -42,6 +42,44 @@ class ScrapeJobCollection extends BaseCollection {
|
|
|
42
42
|
},
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
async allByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
|
|
47
|
+
baseURL,
|
|
48
|
+
processingType,
|
|
49
|
+
optEnableJavascript,
|
|
50
|
+
optHideConsentBanner,
|
|
51
|
+
options = {},
|
|
52
|
+
) {
|
|
53
|
+
const keys = {
|
|
54
|
+
baseURL,
|
|
55
|
+
processingType,
|
|
56
|
+
optEnableJavascript,
|
|
57
|
+
optHideConsentBanner,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return this.allByIndexKeys(keys, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async findByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
|
|
64
|
+
baseURL,
|
|
65
|
+
processingType,
|
|
66
|
+
optEnableJavascript,
|
|
67
|
+
optHideConsentBanner,
|
|
68
|
+
options = {},
|
|
69
|
+
) {
|
|
70
|
+
const jobs = await this
|
|
71
|
+
.allByBaseURLAndProcessingTypeAndOptEnableJavascriptAndOptHideConsentBanner(
|
|
72
|
+
baseURL,
|
|
73
|
+
processingType,
|
|
74
|
+
optEnableJavascript,
|
|
75
|
+
optHideConsentBanner,
|
|
76
|
+
{ ...options, limit: options.limit || 1 },
|
|
77
|
+
);
|
|
78
|
+
if (Array.isArray(jobs)) {
|
|
79
|
+
return jobs[0] || null;
|
|
80
|
+
}
|
|
81
|
+
return jobs || null;
|
|
82
|
+
}
|
|
45
83
|
}
|
|
46
84
|
|
|
47
85
|
export default ScrapeJobCollection;
|
|
@@ -130,17 +130,19 @@ const schema = new SchemaBuilder(ScrapeJob, ScrapeJobCollection)
|
|
|
130
130
|
})
|
|
131
131
|
.addAttribute('optEnableJavascript', {
|
|
132
132
|
type: 'string',
|
|
133
|
+
postgrestIgnore: true,
|
|
133
134
|
hidden: true,
|
|
134
135
|
readOnly: true,
|
|
135
136
|
watch: ['options'],
|
|
136
|
-
set: (_, { options }) => (options[ScrapeJob.ScrapeOptions.ENABLE_JAVASCRIPT] ? 'T' : 'F'),
|
|
137
|
+
set: (_, { options }) => (options?.[ScrapeJob.ScrapeOptions.ENABLE_JAVASCRIPT] ? 'T' : 'F'),
|
|
137
138
|
})
|
|
138
139
|
.addAttribute('optHideConsentBanner', {
|
|
139
140
|
type: 'string',
|
|
141
|
+
postgrestIgnore: true,
|
|
140
142
|
hidden: true,
|
|
141
143
|
readOnly: true,
|
|
142
144
|
watch: ['options'],
|
|
143
|
-
set: (_, { options }) => (options[ScrapeJob.ScrapeOptions.HIDE_CONSENT_BANNER] ? 'T' : 'F'),
|
|
145
|
+
set: (_, { options }) => (options?.[ScrapeJob.ScrapeOptions.HIDE_CONSENT_BANNER] ? 'T' : 'F'),
|
|
144
146
|
})
|
|
145
147
|
// access pattern: get all jobs sorted by startedAt
|
|
146
148
|
.addAllIndex(['startedAt'])
|
|
@@ -22,7 +22,17 @@ import BaseCollection from '../base/base.collection.js';
|
|
|
22
22
|
class SiteEnrollmentCollection extends BaseCollection {
|
|
23
23
|
static COLLECTION_NAME = 'SiteEnrollmentCollection';
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
async create(item, options = {}) {
|
|
26
|
+
if (item?.siteId && item?.entitlementId) {
|
|
27
|
+
const existing = await this.findByIndexKeys({
|
|
28
|
+
siteId: item.siteId,
|
|
29
|
+
entitlementId: item.entitlementId,
|
|
30
|
+
});
|
|
31
|
+
if (existing) return existing;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return super.create(item, options);
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
export default SiteEnrollmentCollection;
|
|
@@ -54,6 +54,10 @@ const schema = new SchemaBuilder(TrialUser, TrialUserCollection)
|
|
|
54
54
|
type: 'any',
|
|
55
55
|
validate: (value) => !value || isObject(value),
|
|
56
56
|
})
|
|
57
|
-
.addAllIndex(['emailId'])
|
|
57
|
+
.addAllIndex(['emailId'])
|
|
58
|
+
.addIndex(
|
|
59
|
+
{ composite: ['organizationId'] },
|
|
60
|
+
{ composite: ['updatedAt'] },
|
|
61
|
+
);
|
|
58
62
|
|
|
59
63
|
export default schema.build();
|
package/src/service/index.d.ts
CHANGED
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
interface DataAccessConfig {
|
|
14
|
-
|
|
14
|
+
postgrestUrl: string;
|
|
15
|
+
postgrestSchema?: string;
|
|
16
|
+
postgrestApiKey?: string;
|
|
17
|
+
postgrestHeaders?: object;
|
|
18
|
+
s3Bucket?: string;
|
|
19
|
+
region?: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
export function createDataAccess(
|
|
18
23
|
config: DataAccessConfig,
|
|
19
24
|
logger: object,
|
|
25
|
+
client?: object,
|
|
20
26
|
): object;
|
package/src/service/index.js
CHANGED
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { DynamoDB } from '@aws-sdk/client-dynamodb';
|
|
14
|
-
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
|
|
15
13
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
16
|
-
import {
|
|
14
|
+
import { PostgrestClient } from '@supabase/postgrest-js';
|
|
17
15
|
|
|
18
16
|
import { instrumentAWSClient } from '@adobe/spacecat-shared-utils';
|
|
19
17
|
import { EntityRegistry } from '../models/index.js';
|
|
@@ -23,47 +21,31 @@ export * from '../errors/index.js';
|
|
|
23
21
|
export * from '../models/index.js';
|
|
24
22
|
export * from '../util/index.js';
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const createRawClient = (client = undefined) => {
|
|
30
|
-
const rawClient = client || (() => {
|
|
31
|
-
if (!defaultDynamoDBClient) {
|
|
32
|
-
defaultDynamoDBClient = new DynamoDB();
|
|
33
|
-
}
|
|
34
|
-
return defaultDynamoDBClient;
|
|
35
|
-
})();
|
|
36
|
-
|
|
37
|
-
let documentClient = documentClientCache.get(rawClient);
|
|
38
|
-
if (!documentClient) {
|
|
39
|
-
documentClient = DynamoDBDocument.from(instrumentAWSClient(rawClient), {
|
|
40
|
-
marshallOptions: {
|
|
41
|
-
convertEmptyValues: true,
|
|
42
|
-
removeUndefinedValues: true,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
documentClientCache.set(rawClient, documentClient);
|
|
24
|
+
const createPostgrestService = (config, client = undefined) => {
|
|
25
|
+
if (client) {
|
|
26
|
+
return client;
|
|
46
27
|
}
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
const {
|
|
30
|
+
postgrestUrl,
|
|
31
|
+
postgrestSchema = 'public',
|
|
32
|
+
postgrestApiKey,
|
|
33
|
+
postgrestHeaders = {},
|
|
34
|
+
} = config;
|
|
35
|
+
|
|
36
|
+
if (!postgrestUrl) {
|
|
37
|
+
throw new Error('postgrestUrl is required to create data access');
|
|
38
|
+
}
|
|
50
39
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const logger = (event) => {
|
|
55
|
-
log.debug(JSON.stringify(event, null, 4));
|
|
40
|
+
const headers = {
|
|
41
|
+
...postgrestHeaders,
|
|
42
|
+
...postgrestApiKey ? { apikey: postgrestApiKey, Authorization: `Bearer ${postgrestApiKey}` } : {},
|
|
56
43
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
client,
|
|
63
|
-
table,
|
|
64
|
-
logger,
|
|
65
|
-
},
|
|
66
|
-
);
|
|
44
|
+
|
|
45
|
+
return new PostgrestClient(postgrestUrl, {
|
|
46
|
+
schema: postgrestSchema,
|
|
47
|
+
headers,
|
|
48
|
+
});
|
|
67
49
|
};
|
|
68
50
|
|
|
69
51
|
/**
|
|
@@ -91,30 +73,29 @@ const createS3Service = (config) => {
|
|
|
91
73
|
* Creates a services dictionary containing all datastore services.
|
|
92
74
|
* Each collection can declare which service it needs via its DATASTORE_TYPE.
|
|
93
75
|
*
|
|
94
|
-
* @param {
|
|
76
|
+
* @param {PostgrestClient} postgrestService - PostgREST client
|
|
95
77
|
* @param {object} config - Configuration object
|
|
96
|
-
* @returns {object} Services dictionary with
|
|
78
|
+
* @returns {object} Services dictionary with postgrest and s3 services
|
|
97
79
|
*/
|
|
98
|
-
const createServices = (
|
|
99
|
-
|
|
80
|
+
const createServices = (postgrestService, config) => ({
|
|
81
|
+
postgrest: postgrestService,
|
|
100
82
|
s3: createS3Service(config),
|
|
101
83
|
});
|
|
102
84
|
|
|
103
85
|
/**
|
|
104
|
-
* Creates a data access layer for interacting with
|
|
86
|
+
* Creates a data access layer for interacting with Postgres via PostgREST.
|
|
105
87
|
*
|
|
106
|
-
* @param {{
|
|
107
|
-
*
|
|
88
|
+
* @param {{postgrestUrl: string, postgrestSchema?: string, postgrestApiKey?: string,
|
|
89
|
+
* postgrestHeaders?: object, s3Bucket?: string, region?: string}} config - Configuration object
|
|
108
90
|
* @param {object} log - Logger instance, defaults to console
|
|
109
|
-
* @param {
|
|
91
|
+
* @param {PostgrestClient} [client] - Optional custom Postgrest client instance
|
|
110
92
|
* @returns {object} Data access collections for interacting with entities
|
|
111
93
|
*/
|
|
112
94
|
export const createDataAccess = (config, log = console, client = undefined) => {
|
|
113
95
|
registerLogger(log);
|
|
114
96
|
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const services = createServices(electroService, config);
|
|
97
|
+
const postgrestService = createPostgrestService(config, client);
|
|
98
|
+
const services = createServices(postgrestService, config);
|
|
118
99
|
const entityRegistry = new EntityRegistry(services, config, log);
|
|
119
100
|
|
|
120
101
|
return entityRegistry.getCollections();
|
package/src/util/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export {
|
|
|
25
25
|
export {
|
|
26
26
|
registerLogger,
|
|
27
27
|
getLogger,
|
|
28
|
+
resetLoggerRegistry,
|
|
28
29
|
} from './logger-registry.js';
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -33,6 +34,6 @@ export {
|
|
|
33
34
|
* @enum {string}
|
|
34
35
|
*/
|
|
35
36
|
export const DATASTORE_TYPE = Object.freeze({
|
|
36
|
-
|
|
37
|
+
POSTGREST: 'postgrest',
|
|
37
38
|
S3: 's3',
|
|
38
39
|
});
|
|
@@ -29,6 +29,10 @@ class LoggerRegistry {
|
|
|
29
29
|
getLogger() {
|
|
30
30
|
return this.#logger || console;
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
reset() {
|
|
34
|
+
this.#logger = null;
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -48,3 +52,11 @@ export function registerLogger(logger) {
|
|
|
48
52
|
export function getLogger() {
|
|
49
53
|
return LoggerRegistry.getInstance().getLogger();
|
|
50
54
|
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resets the registered logger to default console logger.
|
|
58
|
+
* Primarily intended for test isolation.
|
|
59
|
+
*/
|
|
60
|
+
export function resetLoggerRegistry() {
|
|
61
|
+
LoggerRegistry.getInstance().reset();
|
|
62
|
+
}
|