@adobe/spacecat-shared-data-access 2.81.0 → 2.83.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 CHANGED
@@ -1,3 +1,18 @@
1
+ # [@adobe/spacecat-shared-data-access-v2.83.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.82.0...@adobe/spacecat-shared-data-access-v2.83.0) (2025-11-14)
2
+
3
+
4
+ ### Features
5
+
6
+ * Add top-level brandProfile to Site Config with versioning and hash; tests and fixtures ([#1125](https://github.com/adobe/spacecat-shared/issues/1125)) ([6dcd3f8](https://github.com/adobe/spacecat-shared/commit/6dcd3f848d0dcc7303a9778ee86fc334166e2b68))
7
+ * added a new type reporting for fix entity ([#1121](https://github.com/adobe/spacecat-shared/issues/1121)) ([47f44b8](https://github.com/adobe/spacecat-shared/commit/47f44b81f073bdf1801746fb9e348a661f3a3fc5))
8
+
9
+ # [@adobe/spacecat-shared-data-access-v2.82.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.81.0...@adobe/spacecat-shared-data-access-v2.82.0) (2025-11-13)
10
+
11
+
12
+ ### Features
13
+
14
+ * add ahref-paid-import ([#1120](https://github.com/adobe/spacecat-shared/issues/1120)) ([3818671](https://github.com/adobe/spacecat-shared/commit/381867174c60d157c2b5cd9fe68dc6f6a30fb71b))
15
+
1
16
  # [@adobe/spacecat-shared-data-access-v2.81.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v2.80.2...@adobe/spacecat-shared-data-access-v2.81.0) (2025-11-11)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "2.81.0",
3
+ "version": "2.83.0",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -29,9 +29,12 @@ class FixEntity extends BaseModel {
29
29
  ROLLED_BACK: 'ROLLED_BACK', // the fix has been rolled_back
30
30
  };
31
31
 
32
+ // reporting is a new origin which is used
33
+ // to denote the fix entities created by the reporting team
32
34
  static ORIGINS = {
33
35
  SPACECAT: 'spacecat',
34
36
  ASO: 'aso',
37
+ REPORTING: 'reporting',
35
38
  };
36
39
 
37
40
  async getSuggestions() {
@@ -10,7 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import { isNonEmptyObject } from '@adobe/spacecat-shared-utils';
14
+ import crypto from 'crypto';
13
15
  import Joi from 'joi';
16
+
14
17
  import { getLogger } from '../../util/logger-registry.js';
15
18
 
16
19
  export const IMPORT_TYPES = {
@@ -22,6 +25,7 @@ export const IMPORT_TYPES = {
22
25
  ORGANIC_KEYWORDS_QUESTIONS: 'organic-keywords-questions',
23
26
  ORGANIC_TRAFFIC: 'organic-traffic',
24
27
  TOP_PAGES: 'top-pages',
28
+ AHREF_PAID_PAGES: 'ahref-paid-pages',
25
29
  ALL_TRAFFIC: 'all-traffic',
26
30
  CWV_DAILY: 'cwv-daily',
27
31
  CWV_WEEKLY: 'cwv-weekly',
@@ -134,6 +138,13 @@ export const IMPORT_TYPE_SCHEMAS = {
134
138
  limit: Joi.number().integer().min(1).max(2000)
135
139
  .optional(),
136
140
  }),
141
+ [IMPORT_TYPES.AHREF_PAID_PAGES]: Joi.object({
142
+ type: Joi.string().valid(IMPORT_TYPES.AHREF_PAID_PAGES).required(),
143
+ ...IMPORT_BASE_KEYS,
144
+ geo: Joi.string().optional(),
145
+ limit: Joi.number().integer().min(1).max(2000)
146
+ .optional(),
147
+ }),
137
148
  [IMPORT_TYPES.CWV_DAILY]: Joi.object({
138
149
  type: Joi.string().valid(IMPORT_TYPES.CWV_DAILY).required(),
139
150
  ...IMPORT_BASE_KEYS,
@@ -210,6 +221,12 @@ export const DEFAULT_IMPORT_CONFIGS = {
210
221
  enabled: true,
211
222
  geo: 'global',
212
223
  },
224
+ 'ahref-paid-pages': {
225
+ type: 'ahref-paid-pages',
226
+ destinations: ['default'],
227
+ sources: ['ahrefs'],
228
+ enabled: true,
229
+ },
213
230
  'cwv-daily': {
214
231
  type: 'cwv-daily',
215
232
  destinations: ['default'],
@@ -255,6 +272,21 @@ export const configSchema = Joi.object({
255
272
  brandId: Joi.string().required(),
256
273
  userId: Joi.string().required(),
257
274
  }).optional(),
275
+ brandProfile: Joi.object({
276
+ // functional metadata
277
+ version: Joi.number().integer().min(0),
278
+ updatedAt: Joi.string().isoDate(),
279
+ contentHash: Joi.string(),
280
+ // generic top-level content containers (non-strict)
281
+ discovery: Joi.any(),
282
+ clustering: Joi.any(),
283
+ competitive_context: Joi.any(),
284
+ main_profile: Joi.any(),
285
+ sub_brands: Joi.any(),
286
+ confidence_score: Joi.any(),
287
+ pages_considered: Joi.any(),
288
+ diversity_assessment: Joi.any(),
289
+ }).unknown(true).optional(),
258
290
  fetchConfig: Joi.object({
259
291
  headers: Joi.object().pattern(Joi.string(), Joi.string()),
260
292
  overrideBaseURL: Joi.string().uri().optional(),
@@ -400,6 +432,7 @@ export const Config = (data = {}) => {
400
432
  self.getLatestMetrics = (type) => state?.handlers?.[type]?.latestMetrics;
401
433
  self.getFetchConfig = () => state?.fetchConfig;
402
434
  self.getBrandConfig = () => state?.brandConfig;
435
+ self.getBrandProfile = () => state?.brandProfile;
403
436
  self.getCdnLogsConfig = () => state?.cdnLogsConfig;
404
437
  self.getLlmoConfig = () => state?.llmo;
405
438
  self.getLlmoDataFolder = () => state?.llmo?.dataFolder;
@@ -616,6 +649,48 @@ export const Config = (data = {}) => {
616
649
  state.brandConfig = brandConfig;
617
650
  };
618
651
 
652
+ /**
653
+ * Updates the top-level brandProfile with versioning and content hashing.
654
+ * Version is incremented only if the meaningful content changes.
655
+ * @param {object} newProfile
656
+ */
657
+ self.updateBrandProfile = (newProfile = {}) => {
658
+ const prior = state.brandProfile || {};
659
+ // compute hash over all content except functional fields
660
+ const stripFunctional = (p) => {
661
+ if (!isNonEmptyObject(p)) return {};
662
+ const {
663
+ /* eslint-disable no-unused-vars */
664
+ version, updatedAt, contentHash, ...rest
665
+ } = p;
666
+ return rest;
667
+ };
668
+ const meaningful = stripFunctional(newProfile);
669
+ const contentHash = crypto.createHash('sha256')
670
+ .update(JSON.stringify(meaningful))
671
+ .digest('hex');
672
+
673
+ if (prior?.contentHash === contentHash) {
674
+ state.brandProfile = {
675
+ ...prior,
676
+ ...newProfile,
677
+ contentHash: prior.contentHash,
678
+ version: prior.version,
679
+ updatedAt: prior.updatedAt,
680
+ };
681
+ return;
682
+ }
683
+
684
+ const version = (prior?.version || 0) + 1;
685
+ state.brandProfile = {
686
+ ...prior,
687
+ ...meaningful,
688
+ version,
689
+ contentHash,
690
+ updatedAt: new Date().toISOString(),
691
+ };
692
+ };
693
+
619
694
  self.enableImport = (type, config = {}) => {
620
695
  if (!IMPORT_TYPE_SCHEMAS[type]) {
621
696
  throw new Error(`Unknown import type: ${type}`);
@@ -677,6 +752,7 @@ Config.toDynamoItem = (config) => ({
677
752
  imports: config.getImports(),
678
753
  fetchConfig: config.getFetchConfig(),
679
754
  brandConfig: config.getBrandConfig(),
755
+ brandProfile: config.getBrandProfile(),
680
756
  cdnLogsConfig: config.getCdnLogsConfig(),
681
757
  llmo: config.getLlmoConfig(),
682
758
  tokowakaConfig: config.getTokowakaConfig(),
@@ -47,6 +47,7 @@ export type IMPORT_TYPES = {
47
47
  readonly ORGANIC_KEYWORDS: 'organic-keywords';
48
48
  readonly ORGANIC_TRAFFIC: 'organic-traffic';
49
49
  readonly TOP_PAGES: 'top-pages';
50
+ readonly AHREF_PAID_PAGES: 'ahref-paid-pages';
50
51
  readonly TOP_FORMS: 'top-forms';
51
52
  };
52
53
 
@@ -59,7 +60,7 @@ export type IMPORT_SOURCES = {
59
60
  readonly GSC: 'google';
60
61
  };
61
62
 
62
- export type ImportType = 'organic-keywords' | 'organic-traffic' | 'top-pages' | 'top-forms';
63
+ export type ImportType = 'organic-keywords' | 'organic-traffic' | 'top-pages' | 'top-forms' | 'ahref-paid-pages' ;
63
64
  export type ImportDestination = 'default';
64
65
  export type ImportSource = 'ahrefs' | 'google';
65
66