Skip to content

Commit

Permalink
Feature: media server validation (#17591)
Browse files Browse the repository at this point in the history
* implement validation for media and prepare for member

* remove import

* use repository response type

---------

Co-authored-by: Mads Rasmussen <[email protected]>
  • Loading branch information
nielslyngsoe and madsrasmussen authored Dec 13, 2024
1 parent e87d1fc commit 419a8e1
Show file tree
Hide file tree
Showing 17 changed files with 235 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export * from './controller/merge-content-variant-data.controller.js';
export * from './manager/index.js';
export * from './property-dataset-context/index.js';
export * from './workspace/index.js';
export type * from './repository/index.js';
export type * from './types.js';
export type * from './variant-picker/index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { UmbContentDetailModel } from '../types.js';
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository';

export interface UmbContentValidationRepository<DetailModelType extends UmbContentDetailModel> {
validateCreate(model: DetailModelType, parentUnique: string | null): Promise<UmbRepositoryResponse<string>>;
validateSave(model: DetailModelType, variantIds: Array<UmbVariantId>): Promise<UmbRepositoryResponse<string>>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type * from './content-validation-repository.interface.js';
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { UmbContentWorkspaceDataManager } from '../manager/index.js';
import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js';
import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '../variant-picker/index.js';
import type { UmbContentPropertyDatasetContext } from '../property-dataset-context/index.js';
import type { UmbContentValidationRepository } from '../repository/content-validation-repository.interface.js';
import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbraco-cms/backoffice/repository';
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
UMB_VALIDATION_CONTEXT,
UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
UmbDataPathVariantQuery,
UmbServerModelValidatorContext,
UmbValidationContext,
UmbVariantsValidationPathTranslator,
UmbVariantValuesValidationPathTranslator,
Expand All @@ -44,6 +46,7 @@ import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import type { ClassConstructor } from '@umbraco-cms/backoffice/extension-api';

export interface UmbContentDetailWorkspaceContextArgs<
DetailModelType extends UmbContentDetailModel<VariantModelType>,
Expand All @@ -54,6 +57,8 @@ export interface UmbContentDetailWorkspaceContextArgs<
VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel<VariantModelType>,
> extends UmbEntityDetailWorkspaceContextArgs {
contentTypeDetailRepository: UmbDetailRepositoryConstructor<ContentTypeDetailModelType>;
contentValidationRepository?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
skipValidationOnSubmit?: boolean;
contentVariantScaffold: VariantModelType;
saveModalToken?: UmbModalToken<UmbContentVariantPickerData<VariantOptionModelType>, UmbContentVariantPickerValue>;
}
Expand Down Expand Up @@ -118,6 +123,11 @@ export abstract class UmbContentDetailWorkspaceContextBase<
// TODO: fix type error
public readonly variantOptions;

#validateOnSubmit: boolean;
#serverValidation = new UmbServerModelValidatorContext(this);
#validationRepositoryClass?: ClassConstructor<UmbContentValidationRepository<DetailModelType>>;
#validationRepository?: UmbContentValidationRepository<DetailModelType>;

#saveModalToken?: UmbModalToken<UmbContentVariantPickerData<VariantOptionModelType>, UmbContentVariantPickerValue>;

constructor(
Expand All @@ -135,6 +145,8 @@ export abstract class UmbContentDetailWorkspaceContextBase<
this.#saveModalToken = args.saveModalToken;

const contentTypeDetailRepository = new args.contentTypeDetailRepository(this);
this.#validationRepositoryClass = args.contentValidationRepository;
this.#validateOnSubmit = args.skipValidationOnSubmit ? !args.skipValidationOnSubmit : true;
this.structure = new UmbContentTypeStructureManager<ContentTypeDetailModelType>(this, contentTypeDetailRepository);
this.variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture);
this.variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment);
Expand Down Expand Up @@ -470,6 +482,36 @@ export abstract class UmbContentDetailWorkspaceContextBase<
}
}

protected async _askServerToValidate(saveData: DetailModelType, variantIds: Array<UmbVariantId>) {
if (this.#validationRepositoryClass) {
// Create the validation repository if it does not exist. (we first create this here when we need it) [NL]
this.#validationRepository ??= new this.#validationRepositoryClass(this);

// We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL]
if (this.getIsNew()) {
const parent = this.getParent();
if (!parent) throw new Error('Parent is not set');
await this.#serverValidation.askServerForValidation(
saveData,
this.#validationRepository.validateCreate(saveData, parent.unique),
);
} else {
await this.#serverValidation.askServerForValidation(
saveData,
this.#validationRepository.validateSave(saveData, variantIds),
);
}
}
}

/**
* Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted.
* @returns {Promise<void>} a promise which resolves once it has been completed.
*/
public override requestSubmit() {
return this._handleSubmit();
}

public override submit() {
return this._handleSubmit();
}
Expand Down Expand Up @@ -513,7 +555,19 @@ export abstract class UmbContentDetailWorkspaceContextBase<

const saveData = await this._data.constructData(variantIds);
await this._runMandatoryValidationForSaveData(saveData);
await this._performCreateOrUpdate(variantIds, saveData);
if (this.#validateOnSubmit) {
await this._askServerToValidate(saveData, variantIds);
return this.validateAndSubmit(
async () => {
return this._performCreateOrUpdate(variantIds, saveData);
},
async () => {
return this.invalidSubmit();
},
);
} else {
await this._performCreateOrUpdate(variantIds, saveData);
}
}

protected async _performCreateOrUpdate(variantIds: Array<UmbVariantId>, saveData: DetailModelType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { UmbDocumentValidationServerDataSource } from './document-validation.ser
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbContentValidationRepository } from '@umbraco-cms/backoffice/content';

type DetailModelType = UmbDocumentDetailModel;

export class UmbDocumentValidationRepository extends UmbRepositoryBase {
export class UmbDocumentValidationRepository
extends UmbRepositoryBase
implements UmbContentValidationRepository<DetailModelType>
{
#validationDataSource: UmbDocumentValidationServerDataSource;

constructor(host: UmbControllerHost) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@ import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';

/**
* A server data source for Document Validation
* @class UmbDocumentPublishingServerDataSource
* @implements {DocumentTreeDataSource}
*/
export class UmbDocumentValidationServerDataSource {
//#host: UmbControllerHost;

/**
* Creates an instance of UmbDocumentPublishingServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbDocumentPublishingServerDataSource
*/
// TODO: [v15]: ignoring unused var here here to prevent a breaking change
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(host: UmbControllerHost) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UmbServerModelValidatorContext } from '@umbraco-cms/backoffice/validation';
import { UmbDocumentBlueprintDetailRepository } from '@umbraco-cms/backoffice/document-blueprint';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import {
Expand Down Expand Up @@ -62,9 +61,6 @@ export class UmbDocumentWorkspaceContext
{
public readonly publishingRepository = new UmbDocumentPublishingRepository(this);

#serverValidation = new UmbServerModelValidatorContext(this);
#validationRepository?: UmbDocumentValidationRepository;

readonly isTrashed = this._data.createObservablePartOfCurrent((data) => data?.isTrashed);
readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.documentType.unique);
readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent(
Expand All @@ -81,6 +77,8 @@ export class UmbDocumentWorkspaceContext
workspaceAlias: UMB_DOCUMENT_WORKSPACE_ALIAS,
detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS,
contentTypeDetailRepository: UmbDocumentTypeDetailRepository,
contentValidationRepository: UmbDocumentValidationRepository,
skipValidationOnSubmit: true,
contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD,
saveModalToken: UMB_DOCUMENT_SAVE_MODAL,
});
Expand Down Expand Up @@ -206,19 +204,6 @@ export class UmbDocumentWorkspaceContext
this._data.updateCurrent({ template: { unique: templateUnique } });
}

/**
* Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted.
* @returns {Promise<void>} a promise which resolves once it has been completed.
*/
public override requestSubmit() {
return this._handleSubmit();
}

// Because we do not make validation prevent submission this also submits the workspace. [NL]
public override invalidSubmit() {
return this._handleSubmit();
}

async #handleSaveAndPreview() {
const unique = this.getUnique();
if (!unique) throw new Error('Unique is missing');
Expand Down Expand Up @@ -287,23 +272,7 @@ export class UmbDocumentWorkspaceContext
const saveData = await this._data.constructData(variantIds);
await this._runMandatoryValidationForSaveData(saveData);

// Create the validation repository if it does not exist. (we first create this here when we need it) [NL]
this.#validationRepository ??= new UmbDocumentValidationRepository(this);

// We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL]
if (this.getIsNew()) {
const parent = this.getParent();
if (!parent) throw new Error('Parent is not set');
this.#serverValidation.askServerForValidation(
saveData,
this.#validationRepository.validateCreate(saveData, parent.unique),
);
} else {
this.#serverValidation.askServerForValidation(
saveData,
this.#validationRepository.validateSave(saveData, variantIds),
);
}
await this._askServerToValidate(saveData, variantIds);

// TODO: Only validate the specified selection.. [NL]
return this.validateAndSubmit(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import { UmbMediaValidationServerDataSource } from './media-validation.server.da
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbContentValidationRepository } from '@umbraco-cms/backoffice/content';

type DetailModelType = UmbMediaDetailModel;

export class UmbMediaValidationRepository extends UmbRepositoryBase {
export class UmbMediaValidationRepository
extends UmbRepositoryBase
implements UmbContentValidationRepository<DetailModelType>
{
#validationDataSource: UmbMediaValidationServerDataSource;

constructor(host: UmbControllerHost) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';

/**
* A server data source for Media Validatiom
* A server data source for Media Validation
*/
export class UmbMediaValidationServerDataSource {
//#host: UmbControllerHost;

/**
* Creates an instance of UmbDocumentPublishingServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbDocumentPublishingServerDataSource
*/
// TODO: [v15]: ignoring unused var here here to prevent a breaking change
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(host: UmbControllerHost) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js';
import { UMB_MEDIA_DETAIL_REPOSITORY_ALIAS } from '../constants.js';
import type { UmbMediaDetailModel, UmbMediaVariantModel } from '../types.js';
import { UMB_CREATE_MEDIA_WORKSPACE_PATH_PATTERN, UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN } from '../paths.js';
import { UmbMediaValidationRepository } from '../repository/validation/media-validation.repository.js';
import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/constants.js';
import type { UmbMediaDetailRepository } from '../repository/index.js';
import { UMB_MEDIA_WORKSPACE_ALIAS, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js';
Expand Down Expand Up @@ -48,6 +49,7 @@ export class UmbMediaWorkspaceContext
workspaceAlias: UMB_MEDIA_WORKSPACE_ALIAS,
detailRepositoryAlias: UMB_MEDIA_DETAIL_REPOSITORY_ALIAS,
contentTypeDetailRepository: UmbMediaTypeDetailRepository,
contentValidationRepository: UmbMediaValidationRepository,
contentVariantScaffold: UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { UmbMemberDetailRepository } from './detail/index.js';
export { UmbMemberItemRepository, type UmbMemberItemModel } from './item/index.js';
export * from './validation/index.js';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { manifests as detailManifests } from './detail/manifests.js';
import { manifests as itemManifests } from './item/manifests.js';
import { manifests as validationManifests } from './validation/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [...detailManifests, ...itemManifests];
export const manifests: Array<UmbExtensionManifest> = [...detailManifests, ...itemManifests, ...validationManifests];
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { UmbMemberValidationRepository } from './member-validation.repository.js';
export { UMB_MEMBER_VALIDATION_REPOSITORY_ALIAS } from './manifests.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const UMB_MEMBER_VALIDATION_REPOSITORY_ALIAS = 'Umb.Repository.Member.Validation';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_MEMBER_VALIDATION_REPOSITORY_ALIAS,
name: 'Member Validation Repository',
api: () => import('./member-validation.repository.js'),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { UmbMemberDetailModel } from '../../types.js';
import { UmbMemberValidationServerDataSource } from './member-validation.server.data-source.js';
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbContentValidationRepository } from '@umbraco-cms/backoffice/content';

type DetailModelType = UmbMemberDetailModel;

export class UmbMemberValidationRepository
extends UmbRepositoryBase
implements UmbContentValidationRepository<DetailModelType>
{
#validationDataSource: UmbMemberValidationServerDataSource;

constructor(host: UmbControllerHost) {
super(host);

this.#validationDataSource = new UmbMemberValidationServerDataSource(this);
}

/**
* Returns a promise with an observable of the detail for the given unique
* @param {DetailModelType} model - The model to validate
* @param {string | null} [parentUnique] - The parent unique
* @returns {*}
*/
async validateCreate(model: DetailModelType, parentUnique: string | null) {
if (!model) throw new Error('Data is missing');

return this.#validationDataSource.validateCreate(model, parentUnique);
}

/**
* Saves the given data
* @param {DetailModelType} model - The model to save
* @param {Array<UmbVariantId>} variantIds - The variant ids to save
* @returns {*}
*/
async validateSave(model: DetailModelType, variantIds: Array<UmbVariantId>) {
if (!model) throw new Error('Data is missing');
if (!model.unique) throw new Error('Unique is missing');

return this.#validationDataSource.validateUpdate(model, variantIds);
}
}

export { UmbMemberValidationRepository as api };
Loading

0 comments on commit 419a8e1

Please sign in to comment.