@adobe/spacecat-shared-data-access 3.44.0 → 3.45.1

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,16 @@
1
+ ## [@adobe/spacecat-shared-data-access-v3.45.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.45.0...@adobe/spacecat-shared-data-access-v3.45.1) (2026-04-04)
2
+
3
+ ### Bug Fixes
4
+
5
+ * **deps:** update external fixes ([#1506](https://github.com/adobe/spacecat-shared/issues/1506)) ([a4516f6](https://github.com/adobe/spacecat-shared/commit/a4516f68dcb8b2efffc2a0c1e2ec2770347c163d))
6
+
7
+ ## [@adobe/spacecat-shared-data-access-v3.45.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.44.0...@adobe/spacecat-shared-data-access-v3.45.0) (2026-04-04)
8
+
9
+ ### Features
10
+
11
+ * Manage a custom list of audit target URLs ([#1484](https://github.com/adobe/spacecat-shared/issues/1484)) ([06c54d8](https://github.com/adobe/spacecat-shared/commit/06c54d89ea2488f6951cc96b9458521ac9d26706))
12
+ * update suggestion data schemas for some of the opportunities ([#1466](https://github.com/adobe/spacecat-shared/issues/1466)) ([a09e968](https://github.com/adobe/spacecat-shared/commit/a09e968bba9235813451b65defe7d133a8956711))
13
+
1
14
  ## [@adobe/spacecat-shared-data-access-v3.44.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.43.0...@adobe/spacecat-shared-data-access-v3.44.0) (2026-04-02)
2
15
 
3
16
  ### Features
@@ -22,7 +22,7 @@ services:
22
22
  retries: 10
23
23
 
24
24
  postgrest:
25
- image: postgrest/postgrest:v14.7
25
+ image: postgrest/postgrest:v14.8
26
26
  container_name: spacecat-test-postgrest
27
27
  depends_on:
28
28
  db:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/spacecat-shared-data-access",
3
- "version": "3.44.0",
3
+ "version": "3.45.1",
4
4
  "description": "Shared modules of the Spacecat Services - Data Access",
5
5
  "type": "module",
6
6
  "engines": {
@@ -42,11 +42,11 @@
42
42
  "dependencies": {
43
43
  "@adobe/fetch": "^4.2.3",
44
44
  "@adobe/spacecat-shared-utils": "1.105.0",
45
- "@supabase/postgrest-js": "2.100.1",
45
+ "@supabase/postgrest-js": "2.101.1",
46
46
  "@aws-sdk/client-s3": "^3.940.0",
47
47
  "@types/joi": "17.2.3",
48
48
  "aws-xray-sdk": "3.12.0",
49
- "joi": "18.1.1",
49
+ "joi": "18.1.2",
50
50
  "pluralize": "8.0.0"
51
51
  },
52
52
  "devDependencies": {
@@ -400,6 +400,11 @@ export const configSchema = Joi.object({
400
400
  contentAiConfig: Joi.object({
401
401
  index: Joi.string().optional(),
402
402
  }).optional(),
403
+ auditTargetURLs: Joi.object({
404
+ manual: Joi.array().items(Joi.object({
405
+ url: Joi.string().uri().required(),
406
+ })).optional().default([]),
407
+ }).options({ stripUnknown: true }).optional(),
403
408
  handlers: Joi.object().pattern(Joi.string(), Joi.object({
404
409
  mentions: Joi.object().pattern(Joi.string(), Joi.array().items(Joi.string())),
405
410
  excludedURLs: Joi.array().items(Joi.string()),
@@ -510,6 +515,60 @@ export const Config = (data = {}) => {
510
515
  self.getEdgeOptimizeConfig = () => state?.edgeOptimizeConfig;
511
516
  self.getOnboardConfig = () => state?.onboardConfig;
512
517
  self.getCommerceLlmoConfig = () => state?.commerceLlmoConfig;
518
+ const AUDIT_TARGET_SOURCES = ['manual'];
519
+ const auditTargetEntrySchema = Joi.object({
520
+ url: Joi.string().uri().required(),
521
+ });
522
+
523
+ const validateAuditTargetSource = (source) => {
524
+ if (!AUDIT_TARGET_SOURCES.includes(source)) {
525
+ throw new Error(`Invalid audit target source: "${source}". Must be one of: ${AUDIT_TARGET_SOURCES.join(', ')}`);
526
+ }
527
+ };
528
+
529
+ self.getAuditTargetURLsConfig = () => state?.auditTargetURLs;
530
+
531
+ self.getAuditTargetURLs = () => {
532
+ const targets = state?.auditTargetURLs;
533
+ if (!targets) return [];
534
+ return AUDIT_TARGET_SOURCES.flatMap(
535
+ (source) => (targets[source] || []).map((entry) => ({ ...entry, source })),
536
+ );
537
+ };
538
+
539
+ self.getAuditTargetURLsBySource = (source) => {
540
+ validateAuditTargetSource(source);
541
+ return state?.auditTargetURLs?.[source] || [];
542
+ };
543
+
544
+ self.updateAuditTargetURLs = (source, urls) => {
545
+ validateAuditTargetSource(source);
546
+ Joi.assert(urls, Joi.array().items(auditTargetEntrySchema), 'Invalid audit target URLs');
547
+ state.auditTargetURLs = state.auditTargetURLs || {};
548
+ state.auditTargetURLs[source] = urls;
549
+ };
550
+
551
+ self.addAuditTargetURL = (source, urlObj) => {
552
+ validateAuditTargetSource(source);
553
+ Joi.assert(urlObj, auditTargetEntrySchema, 'Invalid audit target URL');
554
+
555
+ state.auditTargetURLs = state.auditTargetURLs || {};
556
+ state.auditTargetURLs[source] = state.auditTargetURLs[source] || [];
557
+ const allUrls = AUDIT_TARGET_SOURCES.flatMap(
558
+ (s) => (state.auditTargetURLs[s] || []).map((e) => e.url),
559
+ );
560
+ if (!allUrls.includes(urlObj.url)) {
561
+ state.auditTargetURLs[source].push(urlObj);
562
+ }
563
+ };
564
+
565
+ self.removeAuditTargetURL = (source, url) => {
566
+ validateAuditTargetSource(source);
567
+ if (!state.auditTargetURLs?.[source]) return;
568
+ state.auditTargetURLs[source] = state.auditTargetURLs[source]
569
+ .filter((t) => t.url !== url);
570
+ };
571
+
513
572
  self.updateSlackConfig = (channel, workspace, invitedUserCount) => {
514
573
  state.slack = {
515
574
  channel,
@@ -864,4 +923,5 @@ Config.toDynamoItem = (config) => ({
864
923
  edgeOptimizeConfig: config.getEdgeOptimizeConfig(),
865
924
  onboardConfig: config.getOnboardConfig?.(),
866
925
  commerceLlmoConfig: config.getCommerceLlmoConfig?.(),
926
+ auditTargetURLs: config.getAuditTargetURLsConfig?.(),
867
927
  });
@@ -99,6 +99,20 @@ export interface LlmoCustomerIntent {
99
99
  value: string;
100
100
  }
101
101
 
102
+ export type AuditTargetSource = 'manual';
103
+
104
+ export interface AuditTargetEntry {
105
+ url: string;
106
+ }
107
+
108
+ export interface AuditTargetEntryWithSource extends AuditTargetEntry {
109
+ source: AuditTargetSource;
110
+ }
111
+
112
+ export interface AuditTargetURLs {
113
+ manual?: AuditTargetEntry[];
114
+ }
115
+
102
116
  export interface SiteConfig {
103
117
  state: {
104
118
  slack?: {
@@ -107,6 +121,7 @@ export interface SiteConfig {
107
121
  invitedUserCount?: number;
108
122
  };
109
123
  imports?: ImportConfig[];
124
+ auditTargetURLs?: AuditTargetURLs;
110
125
  handlers?: Record<string, {
111
126
  mentions?: Record<string, string[]>;
112
127
  excludedURLs?: string[];
@@ -205,6 +220,11 @@ export interface SiteConfig {
205
220
  removeLlmoTag(tag: string): void;
206
221
  getOnboardConfig(): { lastProfile?: string; lastStartTime?: number; forcedOverride?: boolean; history?: Array<{ profile?: string; startTime?: number }> } | undefined;
207
222
  updateOnboardConfig(onboardConfig: { lastProfile?: string; lastStartTime?: number; forcedOverride?: boolean }, options?: { maxHistory?: number }): void;
223
+ getAuditTargetURLs(): AuditTargetEntryWithSource[];
224
+ getAuditTargetURLsBySource(source: AuditTargetSource): AuditTargetEntry[];
225
+ updateAuditTargetURLs(source: AuditTargetSource, urls: AuditTargetEntry[]): void;
226
+ addAuditTargetURL(source: AuditTargetSource, urlObj: AuditTargetEntry): void;
227
+ removeAuditTargetURL(source: AuditTargetSource, url: string): void;
208
228
  }
209
229
 
210
230
  export interface Site extends BaseModel {
@@ -26,6 +26,17 @@
26
26
  import Joi from 'joi';
27
27
  import { OPPORTUNITY_TYPES } from '@adobe/spacecat-shared-utils';
28
28
 
29
+ /**
30
+ * Custom Joi validator that accepts malformed HTTP/HTTPS URLs and relative paths
31
+ * while rejecting dangerous URI schemes (javascript:, data:, blob:, etc.).
32
+ * Used for BROKEN_INTERNAL_LINKS where crawled content may contain malformed URLs.
33
+ */
34
+ const relaxedUrl = Joi.string().min(1).custom((value, helpers) => (
35
+ /^(https?:\/\/|\/)/i.test(value) ? value : helpers.error('string.uriScheme')
36
+ ), 'relaxed URL').messages({
37
+ 'string.uriScheme': '{{#label}} must start with http://, https://, or /',
38
+ });
39
+
29
40
  /**
30
41
  * Data schemas configuration per opportunity type.
31
42
  *
@@ -85,6 +96,8 @@ export const DATA_SCHEMAS = {
85
96
  ).required(),
86
97
  jiraLink: Joi.string().uri().allow(null).optional(),
87
98
  aggregationKey: Joi.string().optional(),
99
+ patchContent: Joi.string().optional(),
100
+ isCodeChangeAvailable: Joi.boolean().optional(),
88
101
  }).unknown(true),
89
102
  projections: {
90
103
  minimal: {
@@ -113,6 +126,8 @@ export const DATA_SCHEMAS = {
113
126
  ).required(),
114
127
  jiraLink: Joi.string().uri().allow(null).optional(),
115
128
  aggregationKey: Joi.string().optional(),
129
+ patchContent: Joi.string().optional(),
130
+ isCodeChangeAvailable: Joi.boolean().optional(),
116
131
  }).unknown(true),
117
132
  projections: {
118
133
  minimal: {
@@ -123,10 +138,14 @@ export const DATA_SCHEMAS = {
123
138
  },
124
139
  },
125
140
  },
141
+ // CWV has two implicit data shapes:
142
+ // 1. Page-level (type='url'): url and issues are present
143
+ // 2. Group-type (type='group'): url is absent, issues may be populated later via update
144
+ // Both shapes share the same schema; url and issues are optional to support both.
126
145
  [OPPORTUNITY_TYPES.CWV]: {
127
146
  schema: Joi.object({
128
147
  type: Joi.string().required(),
129
- url: Joi.string().uri().required(),
148
+ url: Joi.string().uri().optional(),
130
149
  pageviews: Joi.number().optional(),
131
150
  organic: Joi.number().optional(),
132
151
  metrics: Joi.array().items(
@@ -144,7 +163,7 @@ export const DATA_SCHEMAS = {
144
163
  organic: Joi.number().optional(),
145
164
  }).unknown(true),
146
165
  ).required(),
147
- issues: Joi.array().items(Joi.object()).required(),
166
+ issues: Joi.array().items(Joi.object()).optional().default([]),
148
167
  jiraLink: Joi.string().uri().allow(null).optional(),
149
168
  aggregationKey: Joi.string().allow(null).optional(),
150
169
  }).unknown(true),
@@ -163,6 +182,7 @@ export const DATA_SCHEMAS = {
163
182
  Joi.object({
164
183
  isAppropriate: Joi.boolean().optional(),
165
184
  isDecorative: Joi.boolean().optional(),
185
+ hasAltAttribute: Joi.boolean().optional(),
166
186
  xpath: Joi.string().optional(),
167
187
  altText: Joi.string().optional(),
168
188
  imageUrl: Joi.string().uri().optional(),
@@ -376,12 +396,16 @@ export const DATA_SCHEMAS = {
376
396
  [OPPORTUNITY_TYPES.BROKEN_INTERNAL_LINKS]: {
377
397
  schema: Joi.object({
378
398
  // Support both naming conventions (snake_case and camelCase)
379
- url_from: Joi.string().uri().optional(),
380
- urlFrom: Joi.string().uri().optional(),
381
- url_to: Joi.string().uri().optional(),
382
- urlTo: Joi.string().uri().optional(),
399
+ // URL fields use relaxedUrl instead of Joi.string().uri() because
400
+ // internal-links sometimes keeps URL values as-is, including malformed URLs.
401
+ // Unlike BROKEN_BACKLINKS, these accept malformed http/https/relative URLs
402
+ // but reject dangerous schemes (javascript:, data:, blob:, etc.).
403
+ url_from: relaxedUrl.optional(),
404
+ urlFrom: relaxedUrl.optional(),
405
+ url_to: relaxedUrl.optional(),
406
+ urlTo: relaxedUrl.optional(),
383
407
  title: Joi.string().optional(),
384
- urlsSuggested: Joi.array().items(Joi.string().uri()).optional(),
408
+ urlsSuggested: Joi.array().items(relaxedUrl).optional(),
385
409
  aiRationale: Joi.string().optional(),
386
410
  trafficDomain: Joi.number().optional(),
387
411
  priority: Joi.string().optional(),