feat(project-editor): add ProjectValidator with MVP validation rules
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BootstrapConfig } from '../../../shared/models/config';
|
||||
|
||||
export interface ProjectValidationIssue {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const HEX_COLOR = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
||||
const HTTP_URL = /^https?:\/\/\S+$/;
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProjectValidator {
|
||||
validate(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
return [
|
||||
...this.brandingIssues(bootstrap),
|
||||
...this.languageIssues(bootstrap),
|
||||
...this.urlIssues(bootstrap),
|
||||
...this.duplicateSlugIssues(bootstrap),
|
||||
...this.homepageIssues(bootstrap),
|
||||
...this.navigationIssues(bootstrap),
|
||||
...this.colorIssues(bootstrap),
|
||||
];
|
||||
}
|
||||
|
||||
private brandingIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
return bootstrap.branding.logoUrl ? [] : [{ code: 'missing-logo', message: 'builder.validationMissingLogo' }];
|
||||
}
|
||||
|
||||
private languageIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
return bootstrap.localization.supportedLocales.length > 0 ? [] : [{ code: 'no-languages', message: 'builder.validationNoLanguages' }];
|
||||
}
|
||||
|
||||
private urlIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
const url = bootstrap.tenant.websiteBaseUrl;
|
||||
return !url || HTTP_URL.test(url) ? [] : [{ code: 'invalid-url', message: 'builder.validationInvalidUrl' }];
|
||||
}
|
||||
|
||||
private duplicateSlugIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
const staticPages = bootstrap.staticPages;
|
||||
if (!staticPages || Array.isArray(staticPages)) {
|
||||
return [];
|
||||
}
|
||||
const slugs = Object.values(staticPages).map(page => page.slug);
|
||||
const hasDuplicates = slugs.some((slug, index) => slugs.indexOf(slug) !== index);
|
||||
return hasDuplicates ? [{ code: 'duplicate-slugs', message: 'builder.validationDuplicateSlugs' }] : [];
|
||||
}
|
||||
|
||||
private homepageIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
const homePage = bootstrap.pages.find(page => page.key === 'home' || page.route.path === '/');
|
||||
if (!homePage || homePage.sections.length === 0) {
|
||||
return [{ code: 'empty-homepage', message: 'builder.validationEmptyHomepage' }];
|
||||
}
|
||||
const hasMissingWidgetType = homePage.sections.some(section => section.widgets.some(widget => !widget.type?.trim()));
|
||||
return hasMissingWidgetType ? [{ code: 'missing-widget', message: 'builder.validationMissingWidget' }] : [];
|
||||
}
|
||||
|
||||
private navigationIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
const keyOf = (item: { label?: string | Record<string, string>; route?: string }): string =>
|
||||
`${typeof item.label === 'string' ? item.label : JSON.stringify(item.label ?? {})}|${item.route ?? ''}`;
|
||||
const keys = bootstrap.navigation.header.map(keyOf);
|
||||
const hasDuplicates = keys.some((key, index) => keys.indexOf(key) !== index);
|
||||
return hasDuplicates ? [{ code: 'duplicate-nav-links', message: 'builder.validationDuplicateNavLinks' }] : [];
|
||||
}
|
||||
|
||||
private colorIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
|
||||
const invalid = Object.values(bootstrap.theme.palette).some(value => !HEX_COLOR.test(value));
|
||||
return invalid ? [{ code: 'invalid-colors', message: 'builder.validationInvalidColors' }] : [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user