@adobe/spacecat-shared-data-access 2.88.8 → 2.89.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/package.json +1 -1
- package/src/models/audit/audit.model.js +1 -0
- package/src/models/audit-url/audit-url.collection.js +285 -0
- package/src/models/audit-url/audit-url.model.js +93 -0
- package/src/models/audit-url/audit-url.schema.js +75 -0
- package/src/models/audit-url/index.d.ts +52 -0
- package/src/models/audit-url/index.js +19 -0
- package/src/models/base/base.collection.js +5 -0
- package/src/models/base/entity.registry.js +3 -0
- package/src/models/index.js +1 -0
- package/src/util/accessor.utils.js +12 -2
- package/src/util/patcher.js +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-data-access-v2.89.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.88.9...@adobe/spacecat-shared-data-access-v2.89.0) (2025-12-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **audit-url:** add URL store data model and data access ([#1211](https://github.com/adobe/spacecat-shared/issues/1211)) ([a7e458e](https://github.com/adobe/spacecat-shared/commit/a7e458e91d4f1aa93b7243cea5361f028ef2b7db))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-data-access-v2.88.9](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.88.8...@adobe/spacecat-shared-data-access-v2.88.9) (2025-12-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* adding toc audit type ([#1214](https://github.com/adobe/spacecat-shared/issues/1214)) ([7dab7d7](https://github.com/adobe/spacecat-shared/commit/7dab7d7afdcd4b56a56c10b65a3dff0fefb52e4e))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-data-access-v2.88.8](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.88.7...@adobe/spacecat-shared-data-access-v2.88.8) (2025-12-01)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,285 @@
|
|
|
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 } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import BaseCollection from '../base/base.collection.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AuditUrlCollection - A collection class responsible for managing AuditUrl entities.
|
|
19
|
+
* Extends the BaseCollection to provide specific methods for interacting with AuditUrl records.
|
|
20
|
+
*
|
|
21
|
+
* @class AuditUrlCollection
|
|
22
|
+
* @extends BaseCollection
|
|
23
|
+
*/
|
|
24
|
+
class AuditUrlCollection extends BaseCollection {
|
|
25
|
+
static COLLECTION_NAME = 'AuditUrlCollection';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sorts audit URLs by a specified field.
|
|
29
|
+
* @param {Array} auditUrls - Array of AuditUrl objects to sort.
|
|
30
|
+
* @param {string} sortBy - Field to sort by ('url', 'createdAt', 'updatedAt').
|
|
31
|
+
* @param {string} sortOrder - Sort order ('asc' or 'desc'). Default: 'asc'.
|
|
32
|
+
* @returns {Array} Sorted array of AuditUrl objects.
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
static sortAuditUrls(auditUrls, sortBy = 'createdAt', sortOrder = 'asc') {
|
|
36
|
+
if (!auditUrls || auditUrls.length === 0) {
|
|
37
|
+
return auditUrls;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sorted = [...auditUrls].sort((a, b) => {
|
|
41
|
+
let aValue;
|
|
42
|
+
let bValue;
|
|
43
|
+
|
|
44
|
+
// Get values using getter methods if available (with optional chaining)
|
|
45
|
+
switch (sortBy) {
|
|
46
|
+
case 'url':
|
|
47
|
+
aValue = a.getUrl?.() ?? a.url;
|
|
48
|
+
bValue = b.getUrl?.() ?? b.url;
|
|
49
|
+
break;
|
|
50
|
+
case 'createdAt':
|
|
51
|
+
aValue = a.getCreatedAt?.() ?? a.createdAt;
|
|
52
|
+
bValue = b.getCreatedAt?.() ?? b.createdAt;
|
|
53
|
+
break;
|
|
54
|
+
case 'updatedAt':
|
|
55
|
+
aValue = a.getUpdatedAt?.() ?? a.updatedAt;
|
|
56
|
+
bValue = b.getUpdatedAt?.() ?? b.updatedAt;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Handle null/undefined values (push to end)
|
|
63
|
+
if (aValue == null && bValue == null) return 0;
|
|
64
|
+
if (aValue == null) return 1;
|
|
65
|
+
if (bValue == null) return -1;
|
|
66
|
+
|
|
67
|
+
// Compare values
|
|
68
|
+
let comparison = 0;
|
|
69
|
+
if (typeof aValue === 'string' && typeof bValue === 'string') {
|
|
70
|
+
comparison = aValue.localeCompare(bValue);
|
|
71
|
+
/* c8 ignore start */
|
|
72
|
+
// Defensive: all current sort fields return strings,
|
|
73
|
+
// but keep numeric comparison for future extensibility
|
|
74
|
+
} else if (aValue < bValue) {
|
|
75
|
+
comparison = -1;
|
|
76
|
+
} else if (aValue > bValue) {
|
|
77
|
+
comparison = 1;
|
|
78
|
+
} else {
|
|
79
|
+
comparison = 0;
|
|
80
|
+
}
|
|
81
|
+
/* c8 ignore stop */
|
|
82
|
+
|
|
83
|
+
return sortOrder === 'desc' ? -comparison : comparison;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return sorted;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Finds an audit URL by its composite primary key (siteId + url).
|
|
91
|
+
* This is the primary lookup method, similar to LatestAudit.findById(siteId, auditType).
|
|
92
|
+
*
|
|
93
|
+
* @param {string} siteId - The site ID (partition key).
|
|
94
|
+
* @param {string} url - The URL (sort key).
|
|
95
|
+
* @returns {Promise<AuditUrl|null>} The found AuditUrl or null.
|
|
96
|
+
*/
|
|
97
|
+
async findById(siteId, url) {
|
|
98
|
+
if (!hasText(siteId) || !hasText(url)) {
|
|
99
|
+
throw new Error('Both siteId and url are required');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.findByIndexKeys({ siteId, url });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convenience alias for findById.
|
|
107
|
+
* @param {string} siteId - The site ID (partition key).
|
|
108
|
+
* @param {string} url - The URL (sort key).
|
|
109
|
+
* @returns {Promise<AuditUrl|null>} The found AuditUrl or null.
|
|
110
|
+
*/
|
|
111
|
+
async findBySiteIdAndUrl(siteId, url) {
|
|
112
|
+
return this.findById(siteId, url);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets all audit URLs for a site that have a specific audit type enabled.
|
|
117
|
+
* Uses DynamoDB FilterExpression to filter at the database level.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} siteId - The site ID.
|
|
120
|
+
* @param {string} auditType - The audit type to filter by.
|
|
121
|
+
* @param {object} [options={}] - Query options (limit, cursor, sortBy, sortOrder).
|
|
122
|
+
* @returns {Promise<{data: AuditUrl[], cursor: string|null}>} Paginated results.
|
|
123
|
+
*/
|
|
124
|
+
async allBySiteIdAndAuditType(siteId, auditType, options = {}) {
|
|
125
|
+
if (!hasText(siteId) || !hasText(auditType)) {
|
|
126
|
+
throw new Error('Both siteId and auditType are required');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { sortBy, sortOrder, ...queryOptions } = options;
|
|
130
|
+
|
|
131
|
+
// Use FilterExpression to filter at DynamoDB level (reduces network transfer)
|
|
132
|
+
const result = await this.allByIndexKeys(
|
|
133
|
+
{ siteId },
|
|
134
|
+
{
|
|
135
|
+
...queryOptions,
|
|
136
|
+
returnCursor: true,
|
|
137
|
+
where: (attr, op) => op.contains(attr.audits, auditType),
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Handle result format
|
|
142
|
+
const data = result.data || [];
|
|
143
|
+
const { cursor } = result;
|
|
144
|
+
|
|
145
|
+
// Apply sorting if requested
|
|
146
|
+
const sortedData = sortBy
|
|
147
|
+
? AuditUrlCollection.sortAuditUrls(data, sortBy, sortOrder)
|
|
148
|
+
: data;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
data: sortedData,
|
|
152
|
+
cursor,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Gets all audit URLs for a site with sorting support.
|
|
158
|
+
* @param {string} siteId - The site ID.
|
|
159
|
+
* @param {object} [options={}] - Query options (limit, cursor, sortBy, sortOrder).
|
|
160
|
+
* @returns {Promise<{data: AuditUrl[], cursor: string|null}>} Paginated and sorted results.
|
|
161
|
+
*/
|
|
162
|
+
async allBySiteIdSorted(siteId, options = {}) {
|
|
163
|
+
if (!hasText(siteId)) {
|
|
164
|
+
throw new Error('SiteId is required');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { sortBy, sortOrder, ...queryOptions } = options;
|
|
168
|
+
|
|
169
|
+
// Get all URLs for the site with pagination metadata
|
|
170
|
+
const result = await this.allByIndexKeys(
|
|
171
|
+
{ siteId },
|
|
172
|
+
{ ...queryOptions, returnCursor: true },
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Handle result format
|
|
176
|
+
const data = result.data || [];
|
|
177
|
+
const { cursor } = result;
|
|
178
|
+
|
|
179
|
+
// Apply sorting if requested
|
|
180
|
+
const sortedData = sortBy
|
|
181
|
+
? AuditUrlCollection.sortAuditUrls(data, sortBy, sortOrder)
|
|
182
|
+
: data;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
data: sortedData,
|
|
186
|
+
cursor,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Gets all audit URLs for a site by byCustomer flag with sorting support.
|
|
192
|
+
* Uses GSI (spacecat-data-gsi2pk-gsi2sk) for efficient querying.
|
|
193
|
+
* @param {string} siteId - The site ID.
|
|
194
|
+
* @param {boolean} byCustomer - True for customer-added, false for system-added.
|
|
195
|
+
* @param {object} [options={}] - Query options (limit, cursor, sortBy, sortOrder).
|
|
196
|
+
* @returns {Promise<{data: AuditUrl[], cursor: string|null}>} Paginated and sorted results.
|
|
197
|
+
*/
|
|
198
|
+
async allBySiteIdByCustomerSorted(siteId, byCustomer, options = {}) {
|
|
199
|
+
if (!hasText(siteId) || typeof byCustomer !== 'boolean') {
|
|
200
|
+
throw new Error('SiteId is required and byCustomer must be a boolean');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { sortBy, sortOrder, ...queryOptions } = options;
|
|
204
|
+
|
|
205
|
+
// Use GSI for efficient querying by siteId + byCustomer with pagination metadata
|
|
206
|
+
const result = await this.allByIndexKeys(
|
|
207
|
+
{ siteId, byCustomer },
|
|
208
|
+
{
|
|
209
|
+
...queryOptions,
|
|
210
|
+
returnCursor: true,
|
|
211
|
+
index: 'spacecat-data-gsi2pk-gsi2sk',
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Handle result format
|
|
216
|
+
const data = result.data || [];
|
|
217
|
+
const { cursor } = result;
|
|
218
|
+
|
|
219
|
+
// Apply sorting if requested
|
|
220
|
+
const sortedData = sortBy
|
|
221
|
+
? AuditUrlCollection.sortAuditUrls(data, sortBy, sortOrder)
|
|
222
|
+
: data;
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
data: sortedData,
|
|
226
|
+
cursor,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Removes all audit URLs for a specific site.
|
|
232
|
+
* Useful for cleanup operations.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} siteId - The site ID.
|
|
235
|
+
* @returns {Promise<void>}
|
|
236
|
+
*/
|
|
237
|
+
async removeForSiteId(siteId) {
|
|
238
|
+
if (!hasText(siteId)) {
|
|
239
|
+
throw new Error('SiteId is required');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const urlsToRemove = await this.allBySiteId(siteId);
|
|
243
|
+
|
|
244
|
+
if (urlsToRemove.length > 0) {
|
|
245
|
+
// Use composite keys (siteId + url) for deletion
|
|
246
|
+
const keysToRemove = urlsToRemove.map((auditUrl) => ({
|
|
247
|
+
siteId,
|
|
248
|
+
url: auditUrl.getUrl?.() ?? auditUrl.url,
|
|
249
|
+
}));
|
|
250
|
+
await this.removeByIndexKeys(keysToRemove);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Removes audit URLs by byCustomer flag for a specific site.
|
|
256
|
+
* For example, remove all customer-added or all system-added URLs.
|
|
257
|
+
* Uses GSI (spacecat-data-gsi2pk-gsi2sk) for efficient querying.
|
|
258
|
+
*
|
|
259
|
+
* @param {string} siteId - The site ID.
|
|
260
|
+
* @param {boolean} byCustomer - True for customer-added, false for system-added.
|
|
261
|
+
* @returns {Promise<void>}
|
|
262
|
+
*/
|
|
263
|
+
async removeForSiteIdByCustomer(siteId, byCustomer) {
|
|
264
|
+
if (!hasText(siteId) || typeof byCustomer !== 'boolean') {
|
|
265
|
+
throw new Error('SiteId is required and byCustomer must be a boolean');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Use GSI for efficient querying by siteId + byCustomer
|
|
269
|
+
const urlsToRemove = await this.allByIndexKeys(
|
|
270
|
+
{ siteId, byCustomer },
|
|
271
|
+
{ index: 'spacecat-data-gsi2pk-gsi2sk' },
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
if (urlsToRemove.length > 0) {
|
|
275
|
+
// Use composite keys (siteId + url) for deletion
|
|
276
|
+
const keysToRemove = urlsToRemove.map((auditUrl) => ({
|
|
277
|
+
siteId,
|
|
278
|
+
url: auditUrl.getUrl?.() ?? auditUrl.url,
|
|
279
|
+
}));
|
|
280
|
+
await this.removeByIndexKeys(keysToRemove);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export default AuditUrlCollection;
|
|
@@ -0,0 +1,93 @@
|
|
|
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 BaseModel from '../base/base.model.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AuditUrl - A class representing an AuditUrl entity.
|
|
17
|
+
* Provides methods to access and manipulate AuditUrl-specific data.
|
|
18
|
+
*
|
|
19
|
+
* @class AuditUrl
|
|
20
|
+
* @extends BaseModel
|
|
21
|
+
*/
|
|
22
|
+
class AuditUrl extends BaseModel {
|
|
23
|
+
static ENTITY_NAME = 'AuditUrl';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Checks if this URL is enabled for a specific audit type.
|
|
27
|
+
* @param {string} auditType - The audit type to check.
|
|
28
|
+
* @returns {boolean} True if the audit is enabled for this URL.
|
|
29
|
+
*/
|
|
30
|
+
isAuditEnabled(auditType) {
|
|
31
|
+
const audits = this.getAudits?.() ?? this.audits ?? [];
|
|
32
|
+
return audits.includes(auditType);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Adds an audit type to the audits array if not already present.
|
|
37
|
+
* @param {string} auditType - The audit type to add.
|
|
38
|
+
* @returns {this} The current instance for chaining.
|
|
39
|
+
*/
|
|
40
|
+
enableAudit(auditType) {
|
|
41
|
+
const audits = this.getAudits?.() ?? this.audits ?? [];
|
|
42
|
+
if (!audits.includes(auditType)) {
|
|
43
|
+
// Create a new array instead of mutating the existing one
|
|
44
|
+
const updatedAudits = [...audits, auditType];
|
|
45
|
+
if (this.setAudits) {
|
|
46
|
+
this.setAudits(updatedAudits);
|
|
47
|
+
} else {
|
|
48
|
+
this.audits = updatedAudits;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Removes an audit type from the audits array.
|
|
56
|
+
* @param {string} auditType - The audit type to remove.
|
|
57
|
+
* @returns {this} The current instance for chaining.
|
|
58
|
+
*/
|
|
59
|
+
disableAudit(auditType) {
|
|
60
|
+
const audits = this.getAudits?.() ?? this.audits ?? [];
|
|
61
|
+
// filter() already creates a new array
|
|
62
|
+
const filtered = audits.filter((a) => a !== auditType);
|
|
63
|
+
if (this.setAudits) {
|
|
64
|
+
this.setAudits(filtered);
|
|
65
|
+
} else {
|
|
66
|
+
this.audits = filtered;
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks if this URL was added by a customer.
|
|
73
|
+
* @returns {boolean} True if the URL was added by a customer.
|
|
74
|
+
*/
|
|
75
|
+
isCustomerUrl() {
|
|
76
|
+
const byCustomer = this.getByCustomer?.() ?? this.byCustomer;
|
|
77
|
+
return byCustomer === true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generates the composite keys for the AuditUrl model.
|
|
82
|
+
* Required for ElectroDB operations with composite primary key (siteId + url).
|
|
83
|
+
* @returns {Object} - The composite keys.
|
|
84
|
+
*/
|
|
85
|
+
generateCompositeKeys() {
|
|
86
|
+
return {
|
|
87
|
+
siteId: this.getSiteId(),
|
|
88
|
+
url: this.getUrl(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default AuditUrl;
|
|
@@ -0,0 +1,75 @@
|
|
|
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 { isValidUrl } from '@adobe/spacecat-shared-utils';
|
|
16
|
+
|
|
17
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
18
|
+
import AuditUrl from './audit-url.model.js';
|
|
19
|
+
import AuditUrlCollection from './audit-url.collection.js';
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
Schema Doc: https://electrodb.dev/en/modeling/schema/
|
|
23
|
+
Attribute Doc: https://electrodb.dev/en/modeling/attributes/
|
|
24
|
+
Indexes Doc: https://electrodb.dev/en/modeling/indexes/
|
|
25
|
+
|
|
26
|
+
Data Access Patterns:
|
|
27
|
+
1. Get all URLs for a site: allBySiteId(siteId)
|
|
28
|
+
2. Get all URLs for a site by byCustomer: allBySiteIdAndByCustomer(siteId, byCustomer) - uses GSI
|
|
29
|
+
3. Get a specific URL: findById(siteId, url) - uses composite primary key
|
|
30
|
+
4. Get URLs by audit type: filter in code after allBySiteId (audits is a Set)
|
|
31
|
+
|
|
32
|
+
Primary Key (Composite):
|
|
33
|
+
- Partition Key (PK): siteId
|
|
34
|
+
- Sort Key (SK): url
|
|
35
|
+
- This provides natural uniqueness for siteId + url combinations
|
|
36
|
+
- Similar pattern to LatestAudit (siteId + auditType)
|
|
37
|
+
|
|
38
|
+
GSI: spacecat-data-gsi2pk-gsi2sk (byCustomer)
|
|
39
|
+
- Partition Key: siteId
|
|
40
|
+
- Sort Key: byCustomer, url
|
|
41
|
+
- Enables efficient filtering by customer-added vs system-added URLs
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const schema = new SchemaBuilder(AuditUrl, AuditUrlCollection)
|
|
45
|
+
.withPrimaryPartitionKeys(['siteId'])
|
|
46
|
+
.withPrimarySortKeys(['url'])
|
|
47
|
+
.addReference('belongs_to', 'Site')
|
|
48
|
+
.addAttribute('url', {
|
|
49
|
+
type: 'string',
|
|
50
|
+
required: true,
|
|
51
|
+
validate: (value) => isValidUrl(value),
|
|
52
|
+
})
|
|
53
|
+
.addAttribute('byCustomer', {
|
|
54
|
+
type: 'boolean',
|
|
55
|
+
required: true,
|
|
56
|
+
default: true,
|
|
57
|
+
})
|
|
58
|
+
.addIndex(
|
|
59
|
+
{ composite: ['siteId'] },
|
|
60
|
+
{ composite: ['byCustomer', 'url'] },
|
|
61
|
+
)
|
|
62
|
+
.addAttribute('audits', {
|
|
63
|
+
type: 'set',
|
|
64
|
+
items: 'string',
|
|
65
|
+
required: true,
|
|
66
|
+
default: [],
|
|
67
|
+
})
|
|
68
|
+
.addAttribute('createdBy', {
|
|
69
|
+
type: 'string',
|
|
70
|
+
required: true,
|
|
71
|
+
readOnly: true,
|
|
72
|
+
default: 'system',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export default schema.build();
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BaseCollection, BaseModel, Site } from '../index';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AuditUrl entity representing a URL to be audited for a site.
|
|
17
|
+
* Composite primary key: siteId (PK) + url (SK)
|
|
18
|
+
* Similar pattern to LatestAudit (siteId + auditType)
|
|
19
|
+
*/
|
|
20
|
+
export interface AuditUrl extends BaseModel {
|
|
21
|
+
getAudits(): string[];
|
|
22
|
+
getCreatedAt(): string;
|
|
23
|
+
getCreatedBy(): string;
|
|
24
|
+
getUpdatedAt(): string;
|
|
25
|
+
getUpdatedBy(): string;
|
|
26
|
+
getSite(): Promise<Site>;
|
|
27
|
+
getSiteId(): string;
|
|
28
|
+
getByCustomer(): boolean;
|
|
29
|
+
getUrl(): string;
|
|
30
|
+
setAudits(audits: string[]): AuditUrl;
|
|
31
|
+
setSiteId(siteId: string): AuditUrl;
|
|
32
|
+
setByCustomer(byCustomer: boolean): AuditUrl;
|
|
33
|
+
setUrl(url: string): AuditUrl;
|
|
34
|
+
setUpdatedBy(updatedBy: string): AuditUrl;
|
|
35
|
+
isAuditEnabled(auditType: string): boolean;
|
|
36
|
+
enableAudit(auditType: string): AuditUrl;
|
|
37
|
+
disableAudit(auditType: string): AuditUrl;
|
|
38
|
+
isCustomerUrl(): boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AuditUrlCollection extends BaseCollection<AuditUrl> {
|
|
42
|
+
findById(siteId: string, url: string): Promise<AuditUrl | null>;
|
|
43
|
+
findBySiteIdAndUrl(siteId: string, url: string): Promise<AuditUrl | null>;
|
|
44
|
+
allBySiteId(siteId: string): Promise<AuditUrl[]>;
|
|
45
|
+
allBySiteIdAndByCustomer(siteId: string, byCustomer: boolean): Promise<AuditUrl[]>;
|
|
46
|
+
allBySiteIdAndUrl(siteId: string, url: string): Promise<AuditUrl[]>;
|
|
47
|
+
allBySiteIdSorted(siteId: string, options?: { limit?: number; cursor?: string; sortBy?: string; sortOrder?: string }): Promise<{ data: AuditUrl[]; cursor: string | null }>;
|
|
48
|
+
allBySiteIdByCustomerSorted(siteId: string, byCustomer: boolean, options?: { limit?: number; cursor?: string; sortBy?: string; sortOrder?: string }): Promise<{ data: AuditUrl[]; cursor: string | null }>;
|
|
49
|
+
allBySiteIdAndAuditType(siteId: string, auditType: string, options?: { limit?: number; cursor?: string; sortBy?: string; sortOrder?: string }): Promise<{ data: AuditUrl[]; cursor: string | null }>;
|
|
50
|
+
removeForSiteId(siteId: string): Promise<void>;
|
|
51
|
+
removeForSiteIdByCustomer(siteId: string, byCustomer: boolean): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 AuditUrl from './audit-url.model.js';
|
|
14
|
+
import AuditUrlCollection from './audit-url.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
AuditUrl,
|
|
18
|
+
AuditUrlCollection,
|
|
19
|
+
};
|
|
@@ -257,6 +257,11 @@ class BaseCollection {
|
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
+
// Apply where clause (FilterExpression) if provided
|
|
261
|
+
if (typeof options.where === 'function') {
|
|
262
|
+
query = query.where(options.where);
|
|
263
|
+
}
|
|
264
|
+
|
|
260
265
|
// execute the initial query
|
|
261
266
|
let result = await query.go(queryOptions);
|
|
262
267
|
let allData = result.data;
|
|
@@ -16,6 +16,7 @@ import { collectionNameToEntityName, decapitalize } from '../../util/util.js';
|
|
|
16
16
|
import ApiKeyCollection from '../api-key/api-key.collection.js';
|
|
17
17
|
import AsyncJobCollection from '../async-job/async-job.collection.js';
|
|
18
18
|
import AuditCollection from '../audit/audit.collection.js';
|
|
19
|
+
import AuditUrlCollection from '../audit-url/audit-url.collection.js';
|
|
19
20
|
import ConfigurationCollection from '../configuration/configuration.collection.js';
|
|
20
21
|
import ExperimentCollection from '../experiment/experiment.collection.js';
|
|
21
22
|
import EntitlementCollection from '../entitlement/entitlement.collection.js';
|
|
@@ -45,6 +46,7 @@ import PageCitabilityCollection from '../page-citability/page-citability.collect
|
|
|
45
46
|
import ApiKeySchema from '../api-key/api-key.schema.js';
|
|
46
47
|
import AsyncJobSchema from '../async-job/async-job.schema.js';
|
|
47
48
|
import AuditSchema from '../audit/audit.schema.js';
|
|
49
|
+
import AuditUrlSchema from '../audit-url/audit-url.schema.js';
|
|
48
50
|
import ConfigurationSchema from '../configuration/configuration.schema.js';
|
|
49
51
|
import EntitlementSchema from '../entitlement/entitlement.schema.js';
|
|
50
52
|
import FixEntitySchema from '../fix-entity/fix-entity.schema.js';
|
|
@@ -143,6 +145,7 @@ class EntityRegistry {
|
|
|
143
145
|
EntityRegistry.registerEntity(ApiKeySchema, ApiKeyCollection);
|
|
144
146
|
EntityRegistry.registerEntity(AsyncJobSchema, AsyncJobCollection);
|
|
145
147
|
EntityRegistry.registerEntity(AuditSchema, AuditCollection);
|
|
148
|
+
EntityRegistry.registerEntity(AuditUrlSchema, AuditUrlCollection);
|
|
146
149
|
EntityRegistry.registerEntity(ConfigurationSchema, ConfigurationCollection);
|
|
147
150
|
EntityRegistry.registerEntity(EntitlementSchema, EntitlementCollection);
|
|
148
151
|
EntityRegistry.registerEntity(FixEntitySchema, FixEntityCollection);
|
package/src/models/index.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
export * from './api-key/index.js';
|
|
14
14
|
export * from './async-job/index.js';
|
|
15
15
|
export * from './audit/index.js';
|
|
16
|
+
export * from './audit-url/index.js';
|
|
16
17
|
export * from './base/index.js';
|
|
17
18
|
export * from './configuration/index.js';
|
|
18
19
|
export * from './entitlement/index.js';
|
|
@@ -10,13 +10,23 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
hasText, isBoolean, isNonEmptyObject, isNumber,
|
|
15
|
+
} from '@adobe/spacecat-shared-utils';
|
|
14
16
|
|
|
15
17
|
import ValidationError from '../errors/validation.error.js';
|
|
16
18
|
|
|
17
19
|
function validateValue(context, keyName, value) {
|
|
18
20
|
const { type } = context.schema.getAttribute(keyName);
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
let validator;
|
|
23
|
+
if (type === 'number') {
|
|
24
|
+
validator = isNumber;
|
|
25
|
+
} else if (type === 'boolean') {
|
|
26
|
+
validator = isBoolean;
|
|
27
|
+
} else {
|
|
28
|
+
validator = hasText;
|
|
29
|
+
}
|
|
20
30
|
|
|
21
31
|
if (!validator(value)) {
|
|
22
32
|
throw new ValidationError(`${keyName} is required`);
|
package/src/util/patcher.js
CHANGED
|
@@ -137,6 +137,24 @@ class Patcher {
|
|
|
137
137
|
this.updates = { ...this.updates, ...update };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Gets the primary key values for the entity from the schema's primary index.
|
|
142
|
+
* This supports composite primary keys (e.g., siteId + url).
|
|
143
|
+
* @return {Object} - An object containing the primary key values.
|
|
144
|
+
* @private
|
|
145
|
+
*/
|
|
146
|
+
#getPrimaryKeyValues() {
|
|
147
|
+
const primaryKeys = this.schema.getIndexKeys('primary');
|
|
148
|
+
if (isNonEmptyArray(primaryKeys)) {
|
|
149
|
+
return primaryKeys.reduce((acc, key) => {
|
|
150
|
+
acc[key] = this.record[key];
|
|
151
|
+
return acc;
|
|
152
|
+
}, {});
|
|
153
|
+
}
|
|
154
|
+
// Fallback to default id name
|
|
155
|
+
return { [this.idName]: this.record[this.idName] };
|
|
156
|
+
}
|
|
157
|
+
|
|
140
158
|
/**
|
|
141
159
|
* Gets the patch record for the entity. If it does not exist, it will be created.
|
|
142
160
|
* @return {Object} - The patch record for the entity.
|
|
@@ -144,7 +162,7 @@ class Patcher {
|
|
|
144
162
|
*/
|
|
145
163
|
#getPatchRecord() {
|
|
146
164
|
if (!this.patchRecord) {
|
|
147
|
-
this.patchRecord = this.entity.patch(
|
|
165
|
+
this.patchRecord = this.entity.patch(this.#getPrimaryKeyValues());
|
|
148
166
|
}
|
|
149
167
|
return this.patchRecord;
|
|
150
168
|
}
|