Property level validation for Management API (#15644)

* Property level validation for content - initial implementation

* Always succeed create/update regardless of property level validation errors

* Move old complex editor validation classes to Web.BackOffice so they will be deleted

* Include operation status and property validation errors in ProblemDetails

* Refactor property validation to its own service(s)

* Make the problem details builder a little more generic towards extensions

* Validation for item and branch publish

* Moved malplaced test

* Get rid of a TODO

* Integration tests for content validation service

* Simplify validation service

* Add missing response types to create and update for document and media

* Remove test that no longer applies

* Use "errors" for model validation errors (property validation errors)

* Split create/update and validation into their own endpoints

* Fix forward merge

* Correct wrong assumption for missing properties

* Remove localization from validation error messages - decreases dependencies, adds a lot of obsolete constructors

* Reuse existing validation service + support custom error messages

* Fix merge errors

* Review comments
This commit is contained in:
Kenn Jacobsen
2024-01-31 10:40:58 +01:00
committed by GitHub
parent e0e6dee896
commit aaf7075313
86 changed files with 2052 additions and 898 deletions

View File

@@ -3,24 +3,18 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Api.Management.Security.Authorization.Content;
using Umbraco.Cms.Api.Management.ViewModels.Document;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.Document;
[ApiVersion("1.0")]
public class UpdateDocumentController : DocumentControllerBase
public class UpdateDocumentController : UpdateDocumentControllerBase
{
private readonly IAuthorizationService _authorizationService;
private readonly IContentEditingService _contentEditingService;
private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
@@ -30,8 +24,8 @@ public class UpdateDocumentController : DocumentControllerBase
IContentEditingService contentEditingService,
IDocumentEditingPresentationFactory documentEditingPresentationFactory,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
: base(authorizationService, contentEditingService)
{
_authorizationService = authorizationService;
_contentEditingService = contentEditingService;
_documentEditingPresentationFactory = documentEditingPresentationFactory;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
@@ -40,35 +34,17 @@ public class UpdateDocumentController : DocumentControllerBase
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(Guid id, UpdateDocumentRequestModel requestModel)
{
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
User,
ContentPermissionResource.WithKeys(
ActionUpdate.ActionLetter,
id,
requestModel.Variants
.Where(v => v.Culture is not null)
.Select(v => v.Culture!)),
AuthorizationPolicies.ContentPermissionByResource);
if (!authorizationResult.Succeeded)
=> await HandleRequest(id, requestModel, async content =>
{
return Forbidden();
}
ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel);
Attempt<ContentUpdateResult, ContentEditingOperationStatus> result =
await _contentEditingService.UpdateAsync(content, model, CurrentUserKey(_backOfficeSecurityAccessor));
IContent? content = await _contentEditingService.GetAsync(id);
if (content == null)
{
return DocumentNotFound();
}
ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel);
Attempt<IContent, ContentEditingOperationStatus> result = await _contentEditingService.UpdateAsync(content, model, CurrentUserKey(_backOfficeSecurityAccessor));
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Status);
}
return result.Success
? Ok()
: ContentEditingOperationStatusResult(result.Status);
});
}