Skip to content

Commit

Permalink
Enable validation of specific cultures only for document updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kjac committed Sep 18, 2024
1 parent bb3ee49 commit bc2fff1
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace Umbraco.Cms.Api.Management.Controllers.Document;

[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValidateUpdateDocumentController : UpdateDocumentControllerBase
{
private readonly IContentEditingService _contentEditingService;
Expand All @@ -32,10 +33,35 @@ public ValidateUpdateDocumentController(
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[Obsolete("Please use version 1.1 of this API. Will be removed in V16.")]
public async Task<IActionResult> Validate(CancellationToken cancellationToken, Guid id, UpdateDocumentRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel);
var validateUpdateDocumentRequestModel = new ValidateUpdateDocumentRequestModel
{
Values = requestModel.Values,
Variants = requestModel.Variants,
Template = requestModel.Template,
Cultures = null
};
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(validateUpdateDocumentRequestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
return result.Success
? Ok()
: DocumentEditingOperationStatusResult(result.Status, requestModel, result.Result);
});

[HttpPut("{id:guid}/validate")]
[MapToApiVersion("1.1")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ValidateV1_1(CancellationToken cancellationToken, Guid id, ValidateUpdateDocumentRequestModel requestModel)
=> await HandleRequest(id, requestModel, async () =>
{
ValidateContentUpdateModel model = _documentEditingPresentationFactory.MapValidateUpdateModel(requestModel);
Attempt<ContentValidationResult, ContentEditingOperationStatus> result = await _contentEditingService.ValidateUpdateAsync(id, model);
return result.Success
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@ public ContentCreateModel MapCreateModel(CreateDocumentRequestModel requestModel
}

public ContentUpdateModel MapUpdateModel(UpdateDocumentRequestModel requestModel)
=> MapUpdateContentModel<ContentUpdateModel>(requestModel);

public ValidateContentUpdateModel MapValidateUpdateModel(ValidateUpdateDocumentRequestModel requestModel)
{
ValidateContentUpdateModel model = MapUpdateContentModel<ValidateContentUpdateModel>(requestModel);
model.Cultures = requestModel.Cultures;

return model;
}

private TUpdateModel MapUpdateContentModel<TUpdateModel>(UpdateDocumentRequestModel requestModel)
where TUpdateModel : ContentUpdateModel, new()
{
ContentUpdateModel model = MapContentEditingModel<ContentUpdateModel>(requestModel);
TUpdateModel model = MapContentEditingModel<TUpdateModel>(requestModel);
model.TemplateKey = requestModel.Template?.Id;

return model;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ public interface IDocumentEditingPresentationFactory
ContentCreateModel MapCreateModel(CreateDocumentRequestModel requestModel);

ContentUpdateModel MapUpdateModel(UpdateDocumentRequestModel requestModel);

ValidateContentUpdateModel MapValidateUpdateModel(ValidateUpdateDocumentRequestModel requestModel);
}
189 changes: 189 additions & 0 deletions src/Umbraco.Cms.Api.Management/OpenApi.json
Original file line number Diff line number Diff line change
Expand Up @@ -9372,6 +9372,149 @@
}
}
},
"deprecated": true,
"security": [
{
"Backoffice User": [ ]
}
]
}
},
"/umbraco/management/api/v1.1/document/{id}/validate": {
"put": {
"tags": [
"Document"
],
"operationId": "PutUmbracoManagementApiV1.1DocumentByIdValidate1.1",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ValidateUpdateDocumentRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ValidateUpdateDocumentRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ValidateUpdateDocumentRequestModel"
}
]
}
}
}
},
"responses": {
"200": {
"description": "OK",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user do not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice User": [ ]
Expand Down Expand Up @@ -45526,6 +45669,52 @@
},
"additionalProperties": false
},
"ValidateUpdateDocumentRequestModel": {
"required": [
"values",
"variants"
],
"type": "object",
"properties": {
"values": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentValueModel"
}
]
}
},
"variants": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/DocumentVariantRequestModel"
}
]
}
},
"template": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
},
"cultures": {
"uniqueItems": true,
"type": "array",
"items": {
"type": "string"
},
"nullable": true
}
},
"additionalProperties": false
},
"VariantItemResponseModel": {
"required": [
"name"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Document;

public class ValidateUpdateDocumentRequestModel : UpdateDocumentRequestModel
{
public ISet<string>? Cultures { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Core.Models.ContentEditing;

public class ValidateContentUpdateModel : ContentUpdateModel
{
public ISet<string>? Cultures { get; set; }
}
9 changes: 5 additions & 4 deletions src/Umbraco.Core/Services/ContentEditingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ public ContentEditingService(
return await Task.FromResult(content);
}

public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel)
public async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel)
{
IContent? content = ContentService.GetById(key);
return content is not null
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key)
? await ValidateCulturesAndPropertiesAsync(updateModel, content.ContentType.Key, updateModel.Cultures)
: Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentValidationResult());
}

Expand Down Expand Up @@ -137,10 +137,11 @@ public async Task<ContentEditingOperationStatus> SortAsync(Guid? parentKey, IEnu

private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCulturesAndPropertiesAsync(
ContentEditingModelBase contentEditingModelBase,
Guid contentTypeKey)
Guid contentTypeKey,
IEnumerable<string>? culturesToValidate = null)
=> await ValidateCulturesAsync(contentEditingModelBase) is false
? Attempt.FailWithStatus(ContentEditingOperationStatus.InvalidCulture, new ContentValidationResult())
: await ValidatePropertiesAsync(contentEditingModelBase, contentTypeKey);
: await ValidatePropertiesAsync(contentEditingModelBase, contentTypeKey, culturesToValidate);

private async Task<ContentEditingOperationStatus> UpdateTemplateAsync(IContent content, Guid? templateKey)
{
Expand Down
10 changes: 6 additions & 4 deletions src/Umbraco.Core/Services/ContentEditingServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,22 +113,24 @@ protected async Task<bool> ValidateCulturesAsync(ContentEditingModelBase content

protected async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidatePropertiesAsync(
ContentEditingModelBase contentEditingModelBase,
Guid contentTypeKey)
Guid contentTypeKey,
IEnumerable<string>? culturesToValidate = null)
{
TContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey);
if (contentType is null)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.ContentTypeNotFound, new ContentValidationResult());
}

return await ValidatePropertiesAsync(contentEditingModelBase, contentType);
return await ValidatePropertiesAsync(contentEditingModelBase, contentType, culturesToValidate);
}

private async Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidatePropertiesAsync(
ContentEditingModelBase contentEditingModelBase,
TContentType contentType)
TContentType contentType,
IEnumerable<string>? culturesToValidate = null)
{
ContentValidationResult result = await _validationService.ValidatePropertiesAsync(contentEditingModelBase, contentType);
ContentValidationResult result = await _validationService.ValidatePropertiesAsync(contentEditingModelBase, contentType, culturesToValidate);
return result.ValidationErrors.Any() is false
? Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, result)
: Attempt.FailWithStatus(ContentEditingOperationStatus.PropertyValidationError, result);
Expand Down
5 changes: 3 additions & 2 deletions src/Umbraco.Core/Services/ContentValidationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public ContentValidationService(IPropertyValidationService propertyValidationSer

public async Task<ContentValidationResult> ValidatePropertiesAsync(
ContentEditingModelBase contentEditingModelBase,
IContentType contentType)
=> await HandlePropertiesValidationAsync(contentEditingModelBase, contentType);
IContentType contentType,
IEnumerable<string>? culturesToValidate = null)
=> await HandlePropertiesValidationAsync(contentEditingModelBase, contentType, culturesToValidate);
}
5 changes: 3 additions & 2 deletions src/Umbraco.Core/Services/ContentValidationServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ protected ContentValidationServiceBase(

protected async Task<ContentValidationResult> HandlePropertiesValidationAsync(
ContentEditingModelBase contentEditingModelBase,
TContentType contentType)
TContentType contentType,
IEnumerable<string>? culturesToValidate = null)
{
var validationErrors = new List<PropertyValidationError>();

Expand All @@ -43,7 +44,7 @@ protected async Task<ContentValidationResult> HandlePropertiesValidationAsync(
return new ContentValidationResult { ValidationErrors = validationErrors };
}

var cultures = await GetCultureCodes();
var cultures = culturesToValidate?.ToArray() ?? await GetCultureCodes();
// we don't have any managed segments, so we have to make do with the ones passed in the model
var segments = contentEditingModelBase.Variants.DistinctBy(variant => variant.Segment).Select(variant => variant.Segment).ToArray();

Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/IContentEditingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface IContentEditingService

Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateCreateAsync(ContentCreateModel createModel);

Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ContentUpdateModel updateModel);
Task<Attempt<ContentValidationResult, ContentEditingOperationStatus>> ValidateUpdateAsync(Guid key, ValidateContentUpdateModel updateModel);

Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentCreateModel createModel, Guid userKey);

Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/IContentValidationServiceBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Core.Services;
internal interface IContentValidationServiceBase<in TContentType>
where TContentType : IContentTypeComposition
{
Task<ContentValidationResult> ValidatePropertiesAsync(ContentEditingModelBase contentEditingModelBase, TContentType contentType);
Task<ContentValidationResult> ValidatePropertiesAsync(ContentEditingModelBase contentEditingModelBase, TContentType contentType, IEnumerable<string>? culturesToValidate = null);

Task<bool> ValidateCulturesAsync(ContentEditingModelBase contentEditingModelBase);
}
Loading

0 comments on commit bc2fff1

Please sign in to comment.