Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import { hasText } from '@adobe/spacecat-shared-utils';

import BaseCollection from '../base/base.collection.js';

/**
* AuditUrlCollection - A collection class responsible for managing AuditUrl entities.
* Extends the BaseCollection to provide specific methods for interacting with AuditUrl records.
*
* @class AuditUrlCollection
* @extends BaseCollection
*/
class AuditUrlCollection extends BaseCollection {
/**
* Finds an audit URL by site ID and URL.
* This is a convenience method for looking up a specific URL.
*
* @param {string} siteId - The site ID.
* @param {string} url - The URL to find.
* @returns {Promise<AuditUrl|null>} The found AuditUrl or null.
*/
async findBySiteIdAndUrl(siteId, url) {
if (!hasText(siteId) || !hasText(url)) {
throw new Error('Both siteId and url are required');
}

const results = await this.allBySiteIdAndUrl(siteId, url);
return results.length > 0 ? results[0] : null;
}

/**
* Gets all audit URLs for a site that have a specific audit type enabled.
* Note: This performs filtering after retrieval since audits is an array.
*
* @param {string} siteId - The site ID.
* @param {string} auditType - The audit type to filter by.
* @param {object} [options={}] - Query options (limit, cursor).
* @returns {Promise<{items: AuditUrl[], cursor?: string}>} Paginated results.
*/
async allBySiteIdAndAuditType(siteId, auditType, options = {}) {
if (!hasText(siteId) || !hasText(auditType)) {
throw new Error('Both siteId and auditType are required');
}

// Get all URLs for the site
const allUrls = await this.allBySiteId(siteId, options);

// Filter by audit type
const filtered = allUrls.filter((auditUrl) => auditUrl.isAuditEnabled(auditType));

return filtered;
}

/**
* Removes all audit URLs for a specific site.
* Useful for cleanup operations.
*
* @param {string} siteId - The site ID.
* @returns {Promise<void>}
*/
async removeForSiteId(siteId) {
if (!hasText(siteId)) {
throw new Error('SiteId is required');
}

const urlsToRemove = await this.allBySiteId(siteId);
const idsToRemove = urlsToRemove.map((auditUrl) => auditUrl.getId());

if (idsToRemove.length > 0) {
await this.removeByIds(idsToRemove);
}
}

/**
* Removes audit URLs by source for a specific site.
* For example, remove all 'sitemap' sourced URLs.
*
* @param {string} siteId - The site ID.
* @param {string} source - The source to filter by.
* @returns {Promise<void>}
*/
async removeForSiteIdAndSource(siteId, source) {
if (!hasText(siteId) || !hasText(source)) {
throw new Error('Both siteId and source are required');
}

const urlsToRemove = await this.allBySiteIdAndSource(siteId, source);
const idsToRemove = urlsToRemove.map((auditUrl) => auditUrl.getId());

if (idsToRemove.length > 0) {
await this.removeByIds(idsToRemove);
}
}
}

export default AuditUrlCollection;

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import BaseModel from '../base/base.model.js';

/**
* AuditUrl - A class representing an AuditUrl entity.
* Provides methods to access and manipulate AuditUrl-specific data.
*
* @class AuditUrl
* @extends BaseModel
*/
class AuditUrl extends BaseModel {
static DEFAULT_SOURCE = 'manual';

/**
* Checks if this URL is enabled for a specific audit type.
* @param {string} auditType - The audit type to check.
* @returns {boolean} True if the audit is enabled for this URL.
*/
isAuditEnabled(auditType) {
const audits = (this.getAudits ? this.getAudits() : this.audits) || [];
return audits.includes(auditType);
}

/**
* Adds an audit type to the audits array if not already present.
* @param {string} auditType - The audit type to add.
* @returns {this} The current instance for chaining.
*/
enableAudit(auditType) {
const audits = (this.getAudits ? this.getAudits() : this.audits) || [];
if (!audits.includes(auditType)) {
// Create a new array instead of mutating the existing one
const updatedAudits = [...audits, auditType];
if (this.setAudits) {
this.setAudits(updatedAudits);
} else {
this.audits = updatedAudits;
}
}
return this;
}

/**
* Removes an audit type from the audits array.
* @param {string} auditType - The audit type to remove.
* @returns {this} The current instance for chaining.
*/
disableAudit(auditType) {
const audits = (this.getAudits ? this.getAudits() : this.audits) || [];
// filter() already creates a new array
const filtered = audits.filter((a) => a !== auditType);
if (this.setAudits) {
this.setAudits(filtered);
} else {
this.audits = filtered;
}
return this;
}

/**
* Checks if this URL was manually created by a user.
* @returns {boolean} True if the source is manual.
*/
isManualSource() {
const source = this.getSource ? this.getSource() : this.source;
return source === AuditUrl.DEFAULT_SOURCE;
}
}

export default AuditUrl;

Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* c8 ignore start */

import {
isIsoDate,
isValidUrl,
isValidUUID,
} from '@adobe/spacecat-shared-utils';
Comment on lines +15 to +19

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import isValidUUID.

import SchemaBuilder from '../base/schema.builder.js';
import AuditUrl from './audit-url.model.js';
import AuditUrlCollection from './audit-url.collection.js';

/*
Schema Doc: https://electrodb.dev/en/modeling/schema/
Attribute Doc: https://electrodb.dev/en/modeling/attributes/
Indexes Doc: https://electrodb.dev/en/modeling/indexes/

Data Access Patterns:
1. Get all URLs for a site: allBySiteId(siteId)
2. Get all URLs for a site by source: allBySiteIdAndSource(siteId, source)
3. Get a specific URL: allBySiteIdAndUrl(siteId, url)
4. Get URLs by audit type: allBySiteIdAndAuditType(siteId, auditType) - filtered in code

Indexes:
- Primary: siteId (PK) + url (SK) - for unique identification
- bySiteIdAndSource: siteId + source (GSI) - for querying by source
*/

const schema = new SchemaBuilder(AuditUrl, AuditUrlCollection)
.addReference('belongs_to', 'Site', ['url'])
.addAttribute('url', {
type: 'string',
required: true,
validate: (value) => isValidUrl(value),
})
.addAttribute('source', {
type: 'string',
required: true,
default: AuditUrl.DEFAULT_SOURCE,
})
.addAttribute('audits', {
type: 'list',
items: {
type: 'string',
},
required: true,
default: [],
})
.addAttribute('createdAt', {
type: 'string',
required: true,
readOnly: true,
default: () => new Date().toISOString(),
validate: (value) => isIsoDate(value),
})
.addAttribute('updatedAt', {
type: 'string',
required: true,
readOnly: true,
watch: '*',
default: () => new Date().toISOString(),
set: () => new Date().toISOString(),
validate: (value) => isIsoDate(value),
})
.addAttribute('createdBy', {
type: 'string',
required: true,
readOnly: true,
default: 'system',
})
.addAttribute('updatedBy', {
type: 'string',
required: true,
watch: '*',
default: 'system',
set: (value) => value,
})
// Add a second GSI for querying by siteId and source
.addIndex(
{ composite: ['siteId'] },
{ composite: ['source'] },
);

export default schema.build();

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import type { BaseCollection, BaseModel, Site } from '../index';

export interface AuditUrl extends BaseModel {
getAudits(): string[];
getCreatedAt(): string;
getCreatedBy(): string;
getSite(): Promise<Site>;
getSiteId(): string;
getSource(): string;
getUrl(): string;
setAudits(audits: string[]): AuditUrl;
setCreatedBy(createdBy: string): AuditUrl;
setSiteId(siteId: string): AuditUrl;
setSource(source: string): AuditUrl;
setUrl(url: string): AuditUrl;
isAuditEnabled(auditType: string): boolean;
enableAudit(auditType: string): AuditUrl;
disableAudit(auditType: string): AuditUrl;
isManualSource(): boolean;
}

export interface AuditUrlCollection extends BaseCollection<AuditUrl> {
allBySiteId(siteId: string): Promise<AuditUrl[]>;
allBySiteIdAndSource(siteId: string, source: string): Promise<AuditUrl[]>;
allBySiteIdAndSourceAndUrl(siteId: string, source: string, url: string): Promise<AuditUrl[]>;
allBySiteIdAndUrl(siteId: string, url: string): Promise<AuditUrl[]>;
findBySiteId(siteId: string): Promise<AuditUrl | null>;
findBySiteIdAndSource(siteId: string, source: string): Promise<AuditUrl | null>;
findBySiteIdAndSourceAndUrl(siteId: string, source: string, url: string): Promise<AuditUrl | null>;
findBySiteIdAndUrl(siteId: string, url: string): Promise<AuditUrl | null>;
allBySiteIdAndAuditType(siteId: string, auditType: string, options?: object): Promise<AuditUrl[]>;
removeForSiteId(siteId: string): Promise<void>;
removeForSiteIdAndSource(siteId: string, source: string): Promise<void>;
}

20 changes: 20 additions & 0 deletions packages/spacecat-shared-data-access/src/models/audit-url/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import AuditUrl from './audit-url.model.js';
import AuditUrlCollection from './audit-url.collection.js';

export {
AuditUrl,
AuditUrlCollection,
};

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { collectionNameToEntityName, decapitalize } from '../../util/util.js';
import ApiKeyCollection from '../api-key/api-key.collection.js';
import AsyncJobCollection from '../async-job/async-job.collection.js';
import AuditCollection from '../audit/audit.collection.js';
import AuditUrlCollection from '../audit-url/audit-url.collection.js';
import ConfigurationCollection from '../configuration/configuration.collection.js';
import ExperimentCollection from '../experiment/experiment.collection.js';
import EntitlementCollection from '../entitlement/entitlement.collection.js';
Expand Down Expand Up @@ -44,6 +45,7 @@ import TrialUserActivityCollection from '../trial-user-activity/trial-user-activ
import ApiKeySchema from '../api-key/api-key.schema.js';
import AsyncJobSchema from '../async-job/async-job.schema.js';
import AuditSchema from '../audit/audit.schema.js';
import AuditUrlSchema from '../audit-url/audit-url.schema.js';
import ConfigurationSchema from '../configuration/configuration.schema.js';
import EntitlementSchema from '../entitlement/entitlement.schema.js';
import FixEntitySchema from '../fix-entity/fix-entity.schema.js';
Expand Down Expand Up @@ -141,6 +143,7 @@ class EntityRegistry {
EntityRegistry.registerEntity(ApiKeySchema, ApiKeyCollection);
EntityRegistry.registerEntity(AsyncJobSchema, AsyncJobCollection);
EntityRegistry.registerEntity(AuditSchema, AuditCollection);
EntityRegistry.registerEntity(AuditUrlSchema, AuditUrlCollection);
EntityRegistry.registerEntity(ConfigurationSchema, ConfigurationCollection);
EntityRegistry.registerEntity(EntitlementSchema, EntitlementCollection);
EntityRegistry.registerEntity(FixEntitySchema, FixEntityCollection);
Expand Down
1 change: 1 addition & 0 deletions packages/spacecat-shared-data-access/src/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
export * from './api-key/index.js';
export * from './async-job/index.js';
export * from './audit/index.js';
export * from './audit-url/index.js';
export * from './base/index.js';
export * from './configuration/index.js';
export * from './entitlement/index.js';
Expand Down
Loading