@adobe/spacecat-shared-data-access 2.85.1 → 2.87.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/base/entity.registry.js +3 -0
- package/src/models/configuration/configuration.model.js +196 -34
- package/src/models/configuration/configuration.schema.js +1 -1
- package/src/models/configuration/index.js +2 -0
- package/src/models/index.js +1 -0
- package/src/models/page-citability/index.d.ts +39 -0
- package/src/models/page-citability/index.js +19 -0
- package/src/models/page-citability/page-citability.collection.js +25 -0
- package/src/models/page-citability/page-citability.model.js +27 -0
- package/src/models/page-citability/page-citability.schema.js +83 -0
- package/src/models/site/site.schema.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-data-access-v2.87.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.86.0...@adobe/spacecat-shared-data-access-v2.87.0) (2025-11-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add page citability ([#1140](https://github.com/adobe/spacecat-shared/issues/1140)) ([e4dfcdc](https://github.com/adobe/spacecat-shared/commit/e4dfcdccf44ad4a303c3b68b0655e00b25f3da3c))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-data-access-v2.86.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.85.1...@adobe/spacecat-shared-data-access-v2.86.0) (2025-11-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add configuration management APIs ([#1063](https://github.com/adobe/spacecat-shared/issues/1063)) ([d6988a9](https://github.com/adobe/spacecat-shared/commit/d6988a94ad79016c238f3f4d9426c328b52c3131))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-data-access-v2.85.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.85.0...@adobe/spacecat-shared-data-access-v2.85.1) (2025-11-18)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -40,6 +40,7 @@ import PageIntentCollection from '../page-intent/page-intent.collection.js';
|
|
|
40
40
|
import ReportCollection from '../report/report.collection.js';
|
|
41
41
|
import TrialUserCollection from '../trial-user/trial-user.collection.js';
|
|
42
42
|
import TrialUserActivityCollection from '../trial-user-activity/trial-user-activity.collection.js';
|
|
43
|
+
import PageCitabilityCollection from '../page-citability/page-citability.collection.js';
|
|
43
44
|
|
|
44
45
|
import ApiKeySchema from '../api-key/api-key.schema.js';
|
|
45
46
|
import AsyncJobSchema from '../async-job/async-job.schema.js';
|
|
@@ -68,6 +69,7 @@ import PageIntentSchema from '../page-intent/page-intent.schema.js';
|
|
|
68
69
|
import ReportSchema from '../report/report.schema.js';
|
|
69
70
|
import TrialUserSchema from '../trial-user/trial-user.schema.js';
|
|
70
71
|
import TrialUserActivitySchema from '../trial-user-activity/trial-user-activity.schema.js';
|
|
72
|
+
import PageCitabilitySchema from '../page-citability/page-citability.schema.js';
|
|
71
73
|
|
|
72
74
|
/**
|
|
73
75
|
* EntityRegistry - A registry class responsible for managing entities, their schema and collection.
|
|
@@ -165,5 +167,6 @@ EntityRegistry.registerEntity(PageIntentSchema, PageIntentCollection);
|
|
|
165
167
|
EntityRegistry.registerEntity(ReportSchema, ReportCollection);
|
|
166
168
|
EntityRegistry.registerEntity(TrialUserSchema, TrialUserCollection);
|
|
167
169
|
EntityRegistry.registerEntity(TrialUserActivitySchema, TrialUserActivityCollection);
|
|
170
|
+
EntityRegistry.registerEntity(PageCitabilitySchema, PageCitabilityCollection);
|
|
168
171
|
|
|
169
172
|
export default EntityRegistry;
|
|
@@ -14,7 +14,6 @@ import { isNonEmptyObject, isNonEmptyArray } from '@adobe/spacecat-shared-utils'
|
|
|
14
14
|
|
|
15
15
|
import { sanitizeIdAndAuditFields } from '../../util/util.js';
|
|
16
16
|
import BaseModel from '../base/base.model.js';
|
|
17
|
-
import { Audit } from '../audit/index.js';
|
|
18
17
|
import { Entitlement } from '../entitlement/index.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
@@ -47,6 +46,11 @@ class Configuration extends BaseModel {
|
|
|
47
46
|
FORTNIGHTLY_SUNDAY: 'fortnightly-sunday',
|
|
48
47
|
MONTHLY: 'monthly',
|
|
49
48
|
};
|
|
49
|
+
|
|
50
|
+
static AUDIT_NAME_REGEX = /^[a-z0-9-]+$/;
|
|
51
|
+
|
|
52
|
+
static AUDIT_NAME_MAX_LENGTH = 37;
|
|
53
|
+
|
|
50
54
|
// add your custom methods or overrides here
|
|
51
55
|
|
|
52
56
|
getHandler(type) {
|
|
@@ -253,23 +257,186 @@ class Configuration extends BaseModel {
|
|
|
253
257
|
this.updateHandlerOrgs(type, orgId, false);
|
|
254
258
|
}
|
|
255
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Updates the queue URLs configuration by merging with existing queues.
|
|
262
|
+
* Only the specified queue URLs will be updated; others remain unchanged.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} queues - Queue URLs to update (merged with existing)
|
|
265
|
+
* @throws {Error} If queues object is empty or invalid
|
|
266
|
+
*/
|
|
267
|
+
updateQueues(queues) {
|
|
268
|
+
if (!isNonEmptyObject(queues)) {
|
|
269
|
+
throw new Error('Queues configuration cannot be empty');
|
|
270
|
+
}
|
|
271
|
+
const existingQueues = this.getQueues() || {};
|
|
272
|
+
const mergedQueues = { ...existingQueues, ...queues };
|
|
273
|
+
this.setQueues(mergedQueues);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Updates a job's properties (interval, group).
|
|
278
|
+
*
|
|
279
|
+
* @param {string} type - The job type to update
|
|
280
|
+
* @param {object} properties - Properties to update (interval, group)
|
|
281
|
+
* @throws {Error} If job not found or properties are invalid
|
|
282
|
+
*/
|
|
283
|
+
updateJob(type, properties) {
|
|
284
|
+
const jobs = this.getJobs();
|
|
285
|
+
const jobIndex = jobs.findIndex((job) => job.type === type);
|
|
286
|
+
|
|
287
|
+
if (jobIndex === -1) {
|
|
288
|
+
throw new Error(`Job type "${type}" not found in configuration`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (properties.interval && !Object.values(Configuration.JOB_INTERVALS)
|
|
292
|
+
.includes(properties.interval)) {
|
|
293
|
+
throw new Error(`Invalid interval "${properties.interval}". Must be one of: ${Object.values(Configuration.JOB_INTERVALS).join(', ')}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (properties.group && !Object.values(Configuration.JOB_GROUPS).includes(properties.group)) {
|
|
297
|
+
throw new Error(`Invalid group "${properties.group}". Must be one of: ${Object.values(Configuration.JOB_GROUPS).join(', ')}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
jobs[jobIndex] = { ...jobs[jobIndex], ...properties };
|
|
301
|
+
this.setJobs(jobs);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Updates a handler's properties.
|
|
306
|
+
*
|
|
307
|
+
* @param {string} type - The handler type to update
|
|
308
|
+
* @param {object} properties - Properties to update
|
|
309
|
+
* @throws {Error} If handler not found or properties are invalid
|
|
310
|
+
*/
|
|
311
|
+
updateHandlerProperties(type, properties) {
|
|
312
|
+
const handlers = this.getHandlers();
|
|
313
|
+
if (!handlers[type]) {
|
|
314
|
+
throw new Error(`Handler "${type}" not found in configuration`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (properties.productCodes !== undefined) {
|
|
318
|
+
if (!isNonEmptyArray(properties.productCodes)) {
|
|
319
|
+
throw new Error('productCodes must be a non-empty array');
|
|
320
|
+
}
|
|
321
|
+
const validProductCodes = Object.values(Entitlement.PRODUCT_CODES);
|
|
322
|
+
if (!properties.productCodes.every((pc) => validProductCodes.includes(pc))) {
|
|
323
|
+
throw new Error('Invalid product codes provided');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (isNonEmptyArray(properties.dependencies)) {
|
|
328
|
+
for (const dep of properties.dependencies) {
|
|
329
|
+
if (!handlers[dep.handler]) {
|
|
330
|
+
throw new Error(`Dependency handler "${dep.handler}" does not exist in configuration`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (properties.movingAvgThreshold !== undefined && properties.movingAvgThreshold < 1) {
|
|
336
|
+
throw new Error('movingAvgThreshold must be greater than or equal to 1');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (properties.percentageChangeThreshold !== undefined && properties
|
|
340
|
+
.percentageChangeThreshold < 1) {
|
|
341
|
+
throw new Error('percentageChangeThreshold must be greater than or equal to 1');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
handlers[type] = { ...handlers[type], ...properties };
|
|
345
|
+
this.setHandlers(handlers);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Updates the configuration by merging changes into existing sections.
|
|
350
|
+
* This is a flexible update method that allows updating one or more sections at once.
|
|
351
|
+
* Changes are merged, not replaced - existing data is preserved.
|
|
352
|
+
*
|
|
353
|
+
* @param {object} data - Configuration data to update
|
|
354
|
+
* @param {object} [data.handlers] - Handlers to merge (adds new, updates existing)
|
|
355
|
+
* @param {Array} [data.jobs] - Jobs to merge (updates matching jobs by type)
|
|
356
|
+
* @param {object} [data.queues] - Queues to merge (updates specific queue URLs)
|
|
357
|
+
* @throws {Error} If validation fails
|
|
358
|
+
*/
|
|
359
|
+
updateConfiguration(data) {
|
|
360
|
+
if (!isNonEmptyObject(data)) {
|
|
361
|
+
throw new Error('Configuration data cannot be empty');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (data.handlers !== undefined) {
|
|
365
|
+
if (!isNonEmptyObject(data.handlers)) {
|
|
366
|
+
throw new Error('Handlers must be a non-empty object if provided');
|
|
367
|
+
}
|
|
368
|
+
const existingHandlers = this.getHandlers() || {};
|
|
369
|
+
const mergedHandlers = { ...existingHandlers };
|
|
370
|
+
|
|
371
|
+
Object.keys(data.handlers).forEach((handlerType) => {
|
|
372
|
+
mergedHandlers[handlerType] = {
|
|
373
|
+
...existingHandlers[handlerType],
|
|
374
|
+
...data.handlers[handlerType],
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
this.setHandlers(mergedHandlers);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (data.jobs !== undefined) {
|
|
382
|
+
if (!isNonEmptyArray(data.jobs)) {
|
|
383
|
+
throw new Error('Jobs must be a non-empty array if provided');
|
|
384
|
+
}
|
|
385
|
+
const existingJobs = this.getJobs() || [];
|
|
386
|
+
const mergedJobs = [...existingJobs];
|
|
387
|
+
|
|
388
|
+
data.jobs.forEach((newJob) => {
|
|
389
|
+
const existingIndex = mergedJobs.findIndex(
|
|
390
|
+
(job) => job.type === newJob.type && job.group === newJob.group,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (existingIndex !== -1) {
|
|
394
|
+
mergedJobs[existingIndex] = { ...mergedJobs[existingIndex], ...newJob };
|
|
395
|
+
} else {
|
|
396
|
+
mergedJobs.push(newJob);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
this.setJobs(mergedJobs);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (data.queues !== undefined) {
|
|
404
|
+
if (!isNonEmptyObject(data.queues)) {
|
|
405
|
+
throw new Error('Queues must be a non-empty object if provided');
|
|
406
|
+
}
|
|
407
|
+
const existingQueues = this.getQueues() || {};
|
|
408
|
+
const mergedQueues = { ...existingQueues, ...data.queues };
|
|
409
|
+
|
|
410
|
+
this.setQueues(mergedQueues);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
256
414
|
registerAudit(
|
|
257
415
|
type,
|
|
258
416
|
enabledByDefault = false,
|
|
259
417
|
interval = Configuration.JOB_INTERVALS.NEVER,
|
|
260
418
|
productCodes = [],
|
|
261
419
|
) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
420
|
+
if (!type || typeof type !== 'string' || type.trim() === '') {
|
|
421
|
+
throw new Error('Audit type must be a non-empty string');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (type.length > Configuration.AUDIT_NAME_MAX_LENGTH) {
|
|
425
|
+
throw new Error(`Audit type must not exceed ${Configuration.AUDIT_NAME_MAX_LENGTH} characters`);
|
|
426
|
+
}
|
|
427
|
+
if (!Configuration.AUDIT_NAME_REGEX.test(type)) {
|
|
428
|
+
throw new Error('Audit type can only contain lowercase letters, numbers, and hyphens');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const handlers = this.getHandlers();
|
|
432
|
+
if (handlers && handlers[type]) {
|
|
433
|
+
throw new Error(`Audit type "${type}" is already registered`);
|
|
265
434
|
}
|
|
266
435
|
|
|
267
|
-
// Validate job interval
|
|
268
436
|
if (!Object.values(Configuration.JOB_INTERVALS).includes(interval)) {
|
|
269
437
|
throw new Error(`Invalid interval ${interval}`);
|
|
270
438
|
}
|
|
271
439
|
|
|
272
|
-
// Validate product codes
|
|
273
440
|
if (!isNonEmptyArray(productCodes)) {
|
|
274
441
|
throw new Error('No product codes provided');
|
|
275
442
|
}
|
|
@@ -277,26 +444,22 @@ class Configuration extends BaseModel {
|
|
|
277
444
|
throw new Error('Invalid product codes provided');
|
|
278
445
|
}
|
|
279
446
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
this.setHandlers(handlers);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Add to jobs if not already registered
|
|
447
|
+
const updatedHandlers = handlers || {};
|
|
448
|
+
updatedHandlers[type] = {
|
|
449
|
+
enabledByDefault,
|
|
450
|
+
enabled: {
|
|
451
|
+
sites: [],
|
|
452
|
+
orgs: [],
|
|
453
|
+
},
|
|
454
|
+
disabled: {
|
|
455
|
+
sites: [],
|
|
456
|
+
orgs: [],
|
|
457
|
+
},
|
|
458
|
+
dependencies: [],
|
|
459
|
+
productCodes,
|
|
460
|
+
};
|
|
461
|
+
this.setHandlers(updatedHandlers);
|
|
462
|
+
|
|
300
463
|
const jobs = this.getJobs();
|
|
301
464
|
const exists = jobs.find((job) => job.group === 'audits' && job.type === type);
|
|
302
465
|
if (!exists) {
|
|
@@ -310,19 +473,18 @@ class Configuration extends BaseModel {
|
|
|
310
473
|
}
|
|
311
474
|
|
|
312
475
|
unregisterAudit(type) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
throw new Error(`Audit type ${type} is not a valid audit type in the data model`);
|
|
476
|
+
if (!type || typeof type !== 'string' || type.trim() === '') {
|
|
477
|
+
throw new Error('Audit type must be a non-empty string');
|
|
316
478
|
}
|
|
317
479
|
|
|
318
|
-
// Remove from handlers
|
|
319
480
|
const handlers = this.getHandlers();
|
|
320
|
-
if (handlers[type]) {
|
|
321
|
-
|
|
322
|
-
this.setHandlers(handlers);
|
|
481
|
+
if (!handlers || !handlers[type]) {
|
|
482
|
+
throw new Error(`Audit type "${type}" is not registered`);
|
|
323
483
|
}
|
|
324
484
|
|
|
325
|
-
|
|
485
|
+
delete handlers[type];
|
|
486
|
+
this.setHandlers(handlers);
|
|
487
|
+
|
|
326
488
|
const jobs = this.getJobs();
|
|
327
489
|
const jobIndex = jobs.findIndex((job) => job.group === 'audits' && job.type === type);
|
|
328
490
|
if (jobIndex !== -1) {
|
|
@@ -40,7 +40,7 @@ const handlerSchema = Joi.object().pattern(Joi.string(), Joi.object(
|
|
|
40
40
|
actions: Joi.array().items(Joi.string()),
|
|
41
41
|
},
|
|
42
42
|
)),
|
|
43
|
-
productCodes: Joi.array().items(Joi.string()),
|
|
43
|
+
productCodes: Joi.array().items(Joi.string()).min(1).required(),
|
|
44
44
|
},
|
|
45
45
|
)).unknown(true);
|
|
46
46
|
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import Configuration from './configuration.model.js';
|
|
14
14
|
import ConfigurationCollection from './configuration.collection.js';
|
|
15
|
+
import { checkConfiguration } from './configuration.schema.js';
|
|
15
16
|
|
|
16
17
|
export {
|
|
17
18
|
Configuration,
|
|
18
19
|
ConfigurationCollection,
|
|
20
|
+
checkConfiguration,
|
|
19
21
|
};
|
package/src/models/index.js
CHANGED
|
@@ -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, Site } from '../index.js';
|
|
14
|
+
|
|
15
|
+
export interface PageCitability extends BaseModel {
|
|
16
|
+
getSiteId(): string;
|
|
17
|
+
getSite(): Promise<Site>;
|
|
18
|
+
getUrl(): string;
|
|
19
|
+
getCitabilityScore(): number | undefined;
|
|
20
|
+
getContentRatio(): number | undefined;
|
|
21
|
+
getWordDifference(): number | undefined;
|
|
22
|
+
getBotWords(): number | undefined;
|
|
23
|
+
getNormalWords(): number | undefined;
|
|
24
|
+
|
|
25
|
+
setSiteId(siteId: string): PageCitability;
|
|
26
|
+
setUrl(url: string): PageCitability;
|
|
27
|
+
setCitabilityScore(citabilityScore?: number): PageCitability;
|
|
28
|
+
setContentRatio(contentRatio?: number): PageCitability;
|
|
29
|
+
setWordDifference(wordDifference?: number): PageCitability;
|
|
30
|
+
setBotWords(botWords?: number): PageCitability;
|
|
31
|
+
setNormalWords(normalWords?: number): PageCitability;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PageCitabilityCollection extends BaseCollection<PageCitability> {
|
|
35
|
+
allBySiteId(siteId: string): Promise<PageCitability[]>;
|
|
36
|
+
findBySiteId(siteId: string): Promise<PageCitability | null>;
|
|
37
|
+
allByUrl(url: string): Promise<PageCitability[]>;
|
|
38
|
+
findByUrl(url: string): Promise<PageCitability | 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 PageCitability from './page-citability.model.js';
|
|
14
|
+
import PageCitabilityCollection from './page-citability.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
PageCitability,
|
|
18
|
+
PageCitabilityCollection,
|
|
19
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
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 BaseCollection from '../base/base.collection.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PageCitabilityCollection - Manages PageCitability entities.
|
|
17
|
+
*
|
|
18
|
+
* @class PageCitabilityCollection
|
|
19
|
+
* @extends BaseCollection
|
|
20
|
+
*/
|
|
21
|
+
class PageCitabilityCollection extends BaseCollection {
|
|
22
|
+
// add custom collection-level methods here, if needed
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default PageCitabilityCollection;
|
|
@@ -0,0 +1,27 @@
|
|
|
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 BaseModel from '../base/base.model.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PageCitability - Represents a page's citability metrics within a site.
|
|
17
|
+
*
|
|
18
|
+
* @class PageCitability
|
|
19
|
+
* @extends BaseModel
|
|
20
|
+
*/
|
|
21
|
+
class PageCitability extends BaseModel {
|
|
22
|
+
static DEFAULT_UPDATED_BY = 'spacecat';
|
|
23
|
+
|
|
24
|
+
// add any custom methods or overrides here
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default PageCitability;
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { isValidUrl } from '@adobe/spacecat-shared-utils';
|
|
14
|
+
|
|
15
|
+
import SchemaBuilder from '../base/schema.builder.js';
|
|
16
|
+
import PageCitability from './page-citability.model.js';
|
|
17
|
+
import PageCitabilityCollection from './page-citability.collection.js';
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
Schema: https://electrodb.dev/en/modeling/schema/
|
|
21
|
+
Attributes: https://electrodb.dev/en/modeling/attributes/
|
|
22
|
+
Indexes: https://electrodb.dev/en/modeling/indexes/
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const schema = new SchemaBuilder(PageCitability, PageCitabilityCollection)
|
|
26
|
+
// link back to Site entity
|
|
27
|
+
.addReference('belongs_to', 'Site')
|
|
28
|
+
|
|
29
|
+
// page's full URL (must be unique)
|
|
30
|
+
.addAttribute('url', {
|
|
31
|
+
type: 'string',
|
|
32
|
+
required: true,
|
|
33
|
+
validate: (value) => isValidUrl(value),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// citation readability score
|
|
37
|
+
.addAttribute('citabilityScore', {
|
|
38
|
+
type: 'number',
|
|
39
|
+
required: false,
|
|
40
|
+
validate: (value) => !value || (typeof value === 'number' && !Number.isNaN(value)),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// content increase ratio
|
|
44
|
+
.addAttribute('contentRatio', {
|
|
45
|
+
type: 'number',
|
|
46
|
+
required: false,
|
|
47
|
+
validate: (value) => !value || (typeof value === 'number' && !Number.isNaN(value)),
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// word difference between before and after
|
|
51
|
+
.addAttribute('wordDifference', {
|
|
52
|
+
type: 'number',
|
|
53
|
+
required: false,
|
|
54
|
+
validate: (value) => !value || (typeof value === 'number' && !Number.isNaN(value)),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// word count before processing (bot words)
|
|
58
|
+
.addAttribute('botWords', {
|
|
59
|
+
type: 'number',
|
|
60
|
+
required: false,
|
|
61
|
+
validate: (value) => !value || (typeof value === 'number' && !Number.isNaN(value)),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// word count after processing (normal words)
|
|
65
|
+
.addAttribute('normalWords', {
|
|
66
|
+
type: 'number',
|
|
67
|
+
required: false,
|
|
68
|
+
validate: (value) => !value || (typeof value === 'number' && !Number.isNaN(value)),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// optionally track who last updated
|
|
72
|
+
.addAttribute('updatedBy', {
|
|
73
|
+
type: 'string',
|
|
74
|
+
default: PageCitability.DEFAULT_UPDATED_BY,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// allow fetching the single record by its URL
|
|
78
|
+
.addIndex(
|
|
79
|
+
{ composite: ['url'] },
|
|
80
|
+
{ composite: ['updatedAt'] },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
export default schema.build();
|
|
@@ -47,6 +47,7 @@ const schema = new SchemaBuilder(Site, SiteCollection)
|
|
|
47
47
|
.addReference('has_many', 'SiteTopPages')
|
|
48
48
|
.addReference('has_many', 'TrialUserActivities')
|
|
49
49
|
.addReference('has_many', 'PageIntents')
|
|
50
|
+
.addReference('has_many', 'PageCitabilities')
|
|
50
51
|
.addAttribute('baseURL', {
|
|
51
52
|
type: 'string',
|
|
52
53
|
required: true,
|