@adobe/spacecat-shared-data-access 3.43.0 → 3.45.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 +13 -0
- package/package.json +1 -1
- package/src/models/base/entity.registry.js +3 -0
- package/src/models/contact-sales-lead/contact-sales-lead.collection.js +27 -0
- package/src/models/contact-sales-lead/contact-sales-lead.model.js +32 -0
- package/src/models/contact-sales-lead/contact-sales-lead.schema.js +40 -0
- package/src/models/contact-sales-lead/index.d.ts +41 -0
- package/src/models/contact-sales-lead/index.js +19 -0
- package/src/models/index.js +1 -0
- package/src/models/site/config.js +60 -0
- package/src/models/site/index.d.ts +20 -0
- package/src/models/suggestion/suggestion.data-schemas.js +31 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [@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)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* 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))
|
|
6
|
+
* 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))
|
|
7
|
+
|
|
8
|
+
## [@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)
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* add ContactSalesLead entity (SITES-42069) ([#1475](https://github.com/adobe/spacecat-shared/issues/1475)) ([d245781](https://github.com/adobe/spacecat-shared/commit/d2457810e7a01900b2da70b0f81f031f95acdffa))
|
|
13
|
+
|
|
1
14
|
## [@adobe/spacecat-shared-data-access-v3.43.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.42.0...@adobe/spacecat-shared-data-access-v3.43.0) (2026-04-02)
|
|
2
15
|
|
|
3
16
|
### Features
|
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import { collectionNameToEntityName, decapitalize } from '../../util/util.js';
|
|
|
15
15
|
|
|
16
16
|
import ApiKeyCollection from '../api-key/api-key.collection.js';
|
|
17
17
|
import AsyncJobCollection from '../async-job/async-job.collection.js';
|
|
18
|
+
import ContactSalesLeadCollection from '../contact-sales-lead/contact-sales-lead.collection.js';
|
|
18
19
|
import AuditCollection from '../audit/audit.collection.js';
|
|
19
20
|
import AuditUrlCollection from '../audit-url/audit-url.collection.js';
|
|
20
21
|
import ConfigurationCollection from '../configuration/configuration.collection.js';
|
|
@@ -54,6 +55,7 @@ import SiteImsOrgAccessCollection from '../site-ims-org-access/site-ims-org-acce
|
|
|
54
55
|
|
|
55
56
|
import ApiKeySchema from '../api-key/api-key.schema.js';
|
|
56
57
|
import AsyncJobSchema from '../async-job/async-job.schema.js';
|
|
58
|
+
import ContactSalesLeadSchema from '../contact-sales-lead/contact-sales-lead.schema.js';
|
|
57
59
|
import AuditSchema from '../audit/audit.schema.js';
|
|
58
60
|
import AuditUrlSchema from '../audit-url/audit-url.schema.js';
|
|
59
61
|
import ConsumerSchema from '../consumer/consumer.schema.js';
|
|
@@ -187,6 +189,7 @@ class EntityRegistry {
|
|
|
187
189
|
// Register ElectroDB-based entities only (Configuration is handled separately)
|
|
188
190
|
EntityRegistry.registerEntity(ApiKeySchema, ApiKeyCollection);
|
|
189
191
|
EntityRegistry.registerEntity(AsyncJobSchema, AsyncJobCollection);
|
|
192
|
+
EntityRegistry.registerEntity(ContactSalesLeadSchema, ContactSalesLeadCollection);
|
|
190
193
|
EntityRegistry.registerEntity(AuditSchema, AuditCollection);
|
|
191
194
|
EntityRegistry.registerEntity(AuditUrlSchema, AuditUrlCollection);
|
|
192
195
|
EntityRegistry.registerEntity(ConsumerSchema, ConsumerCollection);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 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
|
+
* ContactSalesLeadCollection - A collection class responsible for managing
|
|
17
|
+
* ContactSalesLead entities. Extends the BaseCollection to provide specific
|
|
18
|
+
* methods for interacting with ContactSalesLead records.
|
|
19
|
+
*
|
|
20
|
+
* @class ContactSalesLeadCollection
|
|
21
|
+
* @extends BaseCollection
|
|
22
|
+
*/
|
|
23
|
+
class ContactSalesLeadCollection extends BaseCollection {
|
|
24
|
+
static COLLECTION_NAME = 'ContactSalesLeadCollection';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default ContactSalesLeadCollection;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 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
|
+
* ContactSalesLead - A class representing a contact sales lead entity.
|
|
17
|
+
* Tracks users who have expressed interest in purchasing via the "Contact Sales" flow.
|
|
18
|
+
*
|
|
19
|
+
* @class ContactSalesLead
|
|
20
|
+
* @extends BaseModel
|
|
21
|
+
*/
|
|
22
|
+
class ContactSalesLead extends BaseModel {
|
|
23
|
+
static ENTITY_NAME = 'ContactSalesLead';
|
|
24
|
+
|
|
25
|
+
static STATUSES = {
|
|
26
|
+
NEW: 'NEW',
|
|
27
|
+
CONTACTED: 'CONTACTED',
|
|
28
|
+
CLOSED: 'CLOSED',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default ContactSalesLead;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 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 SchemaBuilder from '../base/schema.builder.js';
|
|
14
|
+
import ContactSalesLead from './contact-sales-lead.model.js';
|
|
15
|
+
import ContactSalesLeadCollection from './contact-sales-lead.collection.js';
|
|
16
|
+
|
|
17
|
+
const schema = new SchemaBuilder(ContactSalesLead, ContactSalesLeadCollection)
|
|
18
|
+
.addReference('belongs_to', 'Organization')
|
|
19
|
+
.addReference('belongs_to', 'Site', [], { required: false })
|
|
20
|
+
.addAttribute('name', {
|
|
21
|
+
type: 'string',
|
|
22
|
+
required: true,
|
|
23
|
+
})
|
|
24
|
+
.addAttribute('email', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
required: true,
|
|
27
|
+
})
|
|
28
|
+
.addAttribute('domain', {
|
|
29
|
+
type: 'string',
|
|
30
|
+
})
|
|
31
|
+
.addAttribute('notes', {
|
|
32
|
+
type: 'string',
|
|
33
|
+
})
|
|
34
|
+
.addAttribute('status', {
|
|
35
|
+
type: Object.values(ContactSalesLead.STATUSES),
|
|
36
|
+
required: true,
|
|
37
|
+
default: ContactSalesLead.STATUSES.NEW,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default schema.build();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 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 {
|
|
14
|
+
BaseCollection, BaseModel, Organization, Site,
|
|
15
|
+
} from '../index';
|
|
16
|
+
|
|
17
|
+
export type ContactSalesLeadStatus = 'NEW' | 'CONTACTED' | 'CLOSED';
|
|
18
|
+
|
|
19
|
+
export interface ContactSalesLead extends BaseModel {
|
|
20
|
+
getName(): string;
|
|
21
|
+
getEmail(): string;
|
|
22
|
+
getDomain(): string | null;
|
|
23
|
+
getSiteId(): string | null;
|
|
24
|
+
getNotes(): string | null;
|
|
25
|
+
getStatus(): ContactSalesLeadStatus;
|
|
26
|
+
getOrganization(): Promise<Organization>;
|
|
27
|
+
getSite(): Promise<Site | null>;
|
|
28
|
+
setName(name: string): ContactSalesLead;
|
|
29
|
+
setEmail(email: string): ContactSalesLead;
|
|
30
|
+
setDomain(domain: string): ContactSalesLead;
|
|
31
|
+
setSiteId(siteId: string): ContactSalesLead;
|
|
32
|
+
setNotes(notes: string): ContactSalesLead;
|
|
33
|
+
setStatus(status: ContactSalesLeadStatus): ContactSalesLead;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ContactSalesLeadCollection extends BaseCollection<ContactSalesLead> {
|
|
37
|
+
allByOrganizationId(organizationId: string): Promise<ContactSalesLead[]>;
|
|
38
|
+
allBySiteId(siteId: string): Promise<ContactSalesLead[]>;
|
|
39
|
+
findByOrganizationId(organizationId: string): Promise<ContactSalesLead | null>;
|
|
40
|
+
findBySiteId(siteId: string): Promise<ContactSalesLead | null>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 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 ContactSalesLead from './contact-sales-lead.model.js';
|
|
14
|
+
import ContactSalesLeadCollection from './contact-sales-lead.collection.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
ContactSalesLead,
|
|
18
|
+
ContactSalesLeadCollection,
|
|
19
|
+
};
|
package/src/models/index.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
export * from './access-grant-log/index.js';
|
|
14
14
|
export * from './api-key/index.js';
|
|
15
15
|
export * from './async-job/index.js';
|
|
16
|
+
export * from './contact-sales-lead/index.js';
|
|
16
17
|
export * from './audit/index.js';
|
|
17
18
|
export * from './audit-url/index.js';
|
|
18
19
|
export * from './base/index.js';
|
|
@@ -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().
|
|
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()).
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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(
|
|
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(),
|