From a9d7f3cbbb2e7c4f40313e0ca480dfec27e240cc Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 24 Aug 2023 09:32:36 +0200 Subject: [PATCH] Content type and media type container CRUD controllers and services (#14682) * CRUD for content and media type folders + refactor data type folder CRUD controllers and services to match * Correct response types + update OpenAPI JSON * Review changes * Review changes * Update OpenAPI JSON after merge --- .../Folder/ByKeyDataTypeFolderController.cs | 4 +- .../Folder/CreateDataTypeFolderController.cs | 11 +- .../Folder/DataTypeFolderControllerBase.cs | 63 +- .../Folder/DeleteDataTypeFolderController.cs | 5 +- .../Folder/UpdateDataTypeFolderController.cs | 8 +- .../ByKeyDocumentTypeFolderController.cs | 25 + .../CreateDocumentTypeFolderController.cs | 29 + .../DeleteDocumentTypeFolderController.cs | 25 + .../DocumentTypeFolderControllerBase.cs | 24 + .../UpdateDocumentTypeFolderController.cs | 27 + .../FolderManagementControllerBase.cs | 94 ++- .../Folder/ByKeyMediaTypeFolderController.cs | 25 + .../Folder/CreateMediaTypeFolderController.cs | 29 + .../Folder/DeleteMediaTypeFolderController.cs | 25 + .../Folder/MediaTypeFolderControllerBase.cs | 24 + .../Folder/UpdateMediaTypeFolderController.cs | 27 + .../BackOfficeAuthPolicyBuilderExtensions.cs | 1 + src/Umbraco.Cms.Api.Management/OpenApi.json | 772 +++++++++++++++++- ...teModel.cs => CreateFolderRequestModel.cs} | 1 + ...eModel.cs => UpdateFolderResponseModel.cs} | 2 +- .../DependencyInjection/UmbracoBuilder.cs | 2 + .../IDataTypeContainerRepository.cs | 3 - .../IEntityContainerRepository.cs | 3 + .../Services/ContentTypeContainerService.cs | 26 + ...peServiceBaseOfTRepositoryTItemTService.cs | 9 + .../Services/DataTypeContainerService.cs | 181 +--- src/Umbraco.Core/Services/DataTypeService.cs | 61 +- .../Services/EntityTypeContainerService.cs | 220 +++++ .../Services/IContentTypeContainerService.cs | 7 + .../Services/IDataTypeContainerService.cs | 42 +- .../Services/IEntityTypeContainerService.cs | 58 ++ .../Services/IMediaTypeContainerService.cs | 7 + .../Services/MediaTypeContainerService.cs | 26 + ...s.cs => EntityContainerOperationStatus.cs} | 2 +- .../MemberTypeContainerRepository.cs | 4 + .../ContentTypeContainerServiceTests.cs | 204 +++++ .../Services/DataTypeContainerServiceTests.cs | 180 ++-- .../Services/DataTypeServiceTests.cs | 14 +- .../MediaTypeContainerServiceTests.cs | 204 +++++ 39 files changed, 1978 insertions(+), 496 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/ByKeyDocumentTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/CreateDocumentTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DeleteDocumentTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DocumentTypeFolderControllerBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/UpdateDocumentTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/ByKeyMediaTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/CreateMediaTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/DeleteMediaTypeFolderController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/MediaTypeFolderControllerBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/UpdateMediaTypeFolderController.cs rename src/Umbraco.Cms.Api.Management/ViewModels/Folder/{FolderCreateModel.cs => CreateFolderRequestModel.cs} (99%) rename src/Umbraco.Cms.Api.Management/ViewModels/Folder/{FolderUpdateModel.cs => UpdateFolderResponseModel.cs} (52%) create mode 100644 src/Umbraco.Core/Services/ContentTypeContainerService.cs create mode 100644 src/Umbraco.Core/Services/EntityTypeContainerService.cs create mode 100644 src/Umbraco.Core/Services/IContentTypeContainerService.cs create mode 100644 src/Umbraco.Core/Services/IEntityTypeContainerService.cs create mode 100644 src/Umbraco.Core/Services/IMediaTypeContainerService.cs create mode 100644 src/Umbraco.Core/Services/MediaTypeContainerService.cs rename src/Umbraco.Core/Services/OperationStatus/{DataTypeContainerOperationStatus.cs => EntityContainerOperationStatus.cs} (83%) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeContainerServiceTests.cs create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeContainerServiceTests.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs index 085a9b8480..a0e4a28122 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/ByKeyDataTypeFolderController.cs @@ -10,7 +10,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; [ApiVersion("1.0")] public class ByKeyDataTypeFolderController : DataTypeFolderControllerBase { - public ByKeyDataTypeFolderController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypeContainerService dataTypeContainerService) + public ByKeyDataTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IDataTypeContainerService dataTypeContainerService) : base(backOfficeSecurityAccessor, dataTypeContainerService) { } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/CreateDataTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/CreateDataTypeFolderController.cs index 7124193a22..a6063f0689 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/CreateDataTypeFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/CreateDataTypeFolderController.cs @@ -10,7 +10,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; [ApiVersion("1.0")] public class CreateDataTypeFolderController : DataTypeFolderControllerBase { - public CreateDataTypeFolderController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypeContainerService dataTypeContainerService) + public CreateDataTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IDataTypeContainerService dataTypeContainerService) : base(backOfficeSecurityAccessor, dataTypeContainerService) { } @@ -18,9 +20,10 @@ public class CreateDataTypeFolderController : DataTypeFolderControllerBase [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Create(CreateFolderRequestModel createFolderRequestModel) - { - return await CreateFolderAsync(createFolderRequestModel, + => await CreateFolderAsync( + createFolderRequestModel, controller => nameof(controller.ByKey)); - } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DataTypeFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DataTypeFolderControllerBase.cs index 6e9e781fe7..1466c2667e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DataTypeFolderControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DataTypeFolderControllerBase.cs @@ -1,13 +1,10 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; @@ -16,58 +13,12 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DataType}/folder")] [ApiExplorerSettings(GroupName = "Data Type")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)] -public abstract class DataTypeFolderControllerBase : FolderManagementControllerBase +public abstract class DataTypeFolderControllerBase : FolderManagementControllerBase { - private readonly IDataTypeContainerService _dataTypeContainerService; - - protected DataTypeFolderControllerBase(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypeContainerService dataTypeContainerService) - : base(backOfficeSecurityAccessor) => - _dataTypeContainerService = dataTypeContainerService; - - protected override Guid ContainerObjectType => Constants.ObjectTypes.DataType; - - protected override async Task GetContainerAsync(Guid id) - => await _dataTypeContainerService.GetAsync(id); - - protected override async Task GetParentContainerAsync(EntityContainer container) - => await _dataTypeContainerService.GetParentAsync(container); - - protected override async Task> CreateContainerAsync(EntityContainer container, Guid? parentId, Guid userKey) - => await _dataTypeContainerService.CreateAsync(container, parentId, userKey); - - protected override async Task> UpdateContainerAsync(EntityContainer container, Guid userKey) - => await _dataTypeContainerService.UpdateAsync(container, userKey); - - protected override async Task> DeleteContainerAsync(Guid id, Guid userKey) - => await _dataTypeContainerService.DeleteAsync(id, userKey); - - protected override IActionResult OperationStatusResult(DataTypeContainerOperationStatus status) - => status switch - { - DataTypeContainerOperationStatus.NotFound => NotFound(new ProblemDetailsBuilder() - .WithTitle("The data type folder could not be found") - .Build()), - DataTypeContainerOperationStatus.ParentNotFound => NotFound(new ProblemDetailsBuilder() - .WithTitle("The data type parent folder could not be found") - .Build()), - DataTypeContainerOperationStatus.DuplicateName => BadRequest(new ProblemDetailsBuilder() - .WithTitle("The name is already used") - .WithDetail("The data type folder name must be unique on this parent.") - .Build()), - DataTypeContainerOperationStatus.DuplicateKey => BadRequest(new ProblemDetailsBuilder() - .WithTitle("The id is already used") - .WithDetail("The data type folder id must be unique.") - .Build()), - DataTypeContainerOperationStatus.NotEmpty => BadRequest(new ProblemDetailsBuilder() - .WithTitle("The folder is not empty") - .WithDetail("The data type folder must be empty to perform this action.") - .Build()), - DataTypeContainerOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder() - .WithTitle("Cancelled by notification") - .WithDetail("A notification handler prevented the data type folder operation.") - .Build()), - _ => StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetailsBuilder() - .WithTitle("Unknown data type folder operation status.") - .Build()), - }; + protected DataTypeFolderControllerBase( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IDataTypeContainerService dataTypeContainerService) + : base(backOfficeSecurityAccessor, dataTypeContainerService) + { + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs index b5fb35acfd..a9d3e7c64f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/DeleteDataTypeFolderController.cs @@ -9,7 +9,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; [ApiVersion("1.0")] public class DeleteDataTypeFolderController : DataTypeFolderControllerBase { - public DeleteDataTypeFolderController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypeContainerService dataTypeContainerService) + public DeleteDataTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IDataTypeContainerService dataTypeContainerService) : base(backOfficeSecurityAccessor, dataTypeContainerService) { } @@ -17,6 +19,7 @@ public class DeleteDataTypeFolderController : DataTypeFolderControllerBase [HttpDelete("{id:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(Guid id) => await DeleteFolderAsync(id); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs index 0524fdec3d..c0d9f1cf00 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Folder/UpdateDataTypeFolderController.cs @@ -10,7 +10,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Folder; [ApiVersion("1.0")] public class UpdateDataTypeFolderController : DataTypeFolderControllerBase { - public UpdateDataTypeFolderController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IDataTypeContainerService dataTypeContainerService) + public UpdateDataTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IDataTypeContainerService dataTypeContainerService) : base(backOfficeSecurityAccessor, dataTypeContainerService) { } @@ -18,6 +20,8 @@ public class UpdateDataTypeFolderController : DataTypeFolderControllerBase [HttpPut("{id:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task Update(Guid id, UpdateFolderReponseModel updateFolderReponseModel) => await UpdateFolderAsync(id, updateFolderReponseModel); + public async Task Update(Guid id, UpdateFolderResponseModel updateFolderResponseModel) + => await UpdateFolderAsync(id, updateFolderResponseModel); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/ByKeyDocumentTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/ByKeyDocumentTypeFolderController.cs new file mode 100644 index 0000000000..b6c677eb8f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/ByKeyDocumentTypeFolderController.cs @@ -0,0 +1,25 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Folder; + +[ApiVersion("1.0")] +public class ByKeyDocumentTypeFolderController : DocumentTypeFolderControllerBase +{ + public ByKeyDocumentTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IContentTypeContainerService contentTypeContainerService) + : base(backOfficeSecurityAccessor, contentTypeContainerService) + { + } + + [HttpGet("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByKey(Guid id) => await GetFolderAsync(id); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/CreateDocumentTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/CreateDocumentTypeFolderController.cs new file mode 100644 index 0000000000..ad88405477 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/CreateDocumentTypeFolderController.cs @@ -0,0 +1,29 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Folder; + +[ApiVersion("1.0")] +public class CreateDocumentTypeFolderController : DocumentTypeFolderControllerBase +{ + public CreateDocumentTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IContentTypeContainerService contentTypeContainerService) + : base(backOfficeSecurityAccessor, contentTypeContainerService) + { + } + + [HttpPost] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Create(CreateFolderRequestModel createFolderRequestModel) + => await CreateFolderAsync( + createFolderRequestModel, + controller => nameof(controller.ByKey)).ConfigureAwait(false); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DeleteDocumentTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DeleteDocumentTypeFolderController.cs new file mode 100644 index 0000000000..3586fa6f39 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DeleteDocumentTypeFolderController.cs @@ -0,0 +1,25 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Folder; + +[ApiVersion("1.0")] +public class DeleteDocumentTypeFolderController : DocumentTypeFolderControllerBase +{ + public DeleteDocumentTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IContentTypeContainerService contentTypeContainerService) + : base(backOfficeSecurityAccessor, contentTypeContainerService) + { + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Delete(Guid id) => await DeleteFolderAsync(id); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DocumentTypeFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DocumentTypeFolderControllerBase.cs new file mode 100644 index 0000000000..01488d86d7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/DocumentTypeFolderControllerBase.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Folder; + +[ApiController] +[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DocumentType}/folder")] +[ApiExplorerSettings(GroupName = "Document Type")] +[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)] +public abstract class DocumentTypeFolderControllerBase : FolderManagementControllerBase +{ + protected DocumentTypeFolderControllerBase( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IContentTypeContainerService contentTypeContainerService) + : base(backOfficeSecurityAccessor, contentTypeContainerService) + { + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/UpdateDocumentTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/UpdateDocumentTypeFolderController.cs new file mode 100644 index 0000000000..4dc049effe --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Folder/UpdateDocumentTypeFolderController.cs @@ -0,0 +1,27 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Folder; + +[ApiVersion("1.0")] +public class UpdateDocumentTypeFolderController : DocumentTypeFolderControllerBase +{ + public UpdateDocumentTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IContentTypeContainerService contentTypeContainerService) + : base(backOfficeSecurityAccessor, contentTypeContainerService) + { + } + + [HttpPut("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Update(Guid id, UpdateFolderResponseModel updateFolderResponseModel) + => await UpdateFolderAsync(id, updateFolderResponseModel); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs index b1bb79b2a1..6d97b358a7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs @@ -1,24 +1,32 @@ using System.Linq.Expressions; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.ViewModels.Folder; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers; -public abstract class FolderManagementControllerBase : ManagementApiControllerBase - where TStatus : Enum +public abstract class FolderManagementControllerBase : ManagementApiControllerBase + where TTreeEntity : ITreeEntity { private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IEntityTypeContainerService _treeEntityTypeContainerService; - protected FolderManagementControllerBase(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) - => _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + protected FolderManagementControllerBase(IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IEntityTypeContainerService treeEntityTypeContainerService) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _treeEntityTypeContainerService = treeEntityTypeContainerService; + } protected async Task GetFolderAsync(Guid key) { - EntityContainer? container = await GetContainerAsync(key); + EntityContainer? container = await _treeEntityTypeContainerService.GetAsync(key); if (container == null) { return NotFound(new ProblemDetailsBuilder() @@ -26,7 +34,7 @@ public abstract class FolderManagementControllerBase : ManagementApiCon .Build()); } - EntityContainer? parentContainer = await GetParentContainerAsync(container); + EntityContainer? parentContainer = await _treeEntityTypeContainerService.GetParentAsync(container); // we could implement a mapper for this but it seems rather overkill at this point return Ok(new FolderResponseModel @@ -41,36 +49,24 @@ public abstract class FolderManagementControllerBase : ManagementApiCon CreateFolderRequestModel createFolderRequestModel, Expression> createdAction) { - var container = new EntityContainer(ContainerObjectType) { Name = createFolderRequestModel.Name }; - - if (createFolderRequestModel.Id.HasValue) - { - container.Key = createFolderRequestModel.Id.Value; - } - - Attempt result = await CreateContainerAsync( - container, + Attempt result = await _treeEntityTypeContainerService.CreateAsync( + createFolderRequestModel.Id, + createFolderRequestModel.Name, createFolderRequestModel.ParentId, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(createdAction, result.Result.Key) + ? CreatedAtAction(createdAction, result.Result!.Key) : OperationStatusResult(result.Status); } - protected async Task UpdateFolderAsync(Guid key, UpdateFolderReponseModel updateFolderReponseModel) + protected async Task UpdateFolderAsync(Guid key, UpdateFolderResponseModel updateFolderResponseModel) { - EntityContainer? container = await GetContainerAsync(key); - if (container == null) - { - return NotFound(new ProblemDetailsBuilder() - .WithTitle($"Could not find the folder with key: {key}") - .Build()); - } + Attempt result = await _treeEntityTypeContainerService.UpdateAsync( + key, + updateFolderResponseModel.Name, + CurrentUserKey(_backOfficeSecurityAccessor)); - container.Name = updateFolderReponseModel.Name; - - Attempt result = await UpdateContainerAsync(container, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success ? Ok() : OperationStatusResult(result.Status); @@ -78,23 +74,39 @@ public abstract class FolderManagementControllerBase : ManagementApiCon protected async Task DeleteFolderAsync(Guid key) { - Attempt result = await DeleteContainerAsync(key, CurrentUserKey(_backOfficeSecurityAccessor)); + Attempt result = await _treeEntityTypeContainerService.DeleteAsync(key, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success ? Ok() : OperationStatusResult(result.Status); } - protected abstract Guid ContainerObjectType { get; } - - protected abstract Task GetContainerAsync(Guid key); - - protected abstract Task GetParentContainerAsync(EntityContainer container); - - protected abstract Task> CreateContainerAsync(EntityContainer container, Guid? parentId, Guid userKey); - - protected abstract Task> UpdateContainerAsync(EntityContainer container, Guid userKey); - - protected abstract Task> DeleteContainerAsync(Guid id, Guid userKey); - - protected abstract IActionResult OperationStatusResult(TStatus status); + protected IActionResult OperationStatusResult(EntityContainerOperationStatus status) + => status switch + { + EntityContainerOperationStatus.NotFound => NotFound(new ProblemDetailsBuilder() + .WithTitle("The folder could not be found") + .Build()), + EntityContainerOperationStatus.ParentNotFound => NotFound(new ProblemDetailsBuilder() + .WithTitle("The parent folder could not be found") + .Build()), + EntityContainerOperationStatus.DuplicateName => BadRequest(new ProblemDetailsBuilder() + .WithTitle("The name is already used") + .WithDetail("The folder name must be unique on this parent.") + .Build()), + EntityContainerOperationStatus.DuplicateKey => BadRequest(new ProblemDetailsBuilder() + .WithTitle("The id is already used") + .WithDetail("The folder id must be unique.") + .Build()), + EntityContainerOperationStatus.NotEmpty => BadRequest(new ProblemDetailsBuilder() + .WithTitle("The folder is not empty") + .WithDetail("The folder must be empty to perform this action.") + .Build()), + EntityContainerOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Cancelled by notification") + .WithDetail("A notification handler prevented the folder operation.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetailsBuilder() + .WithTitle("Unknown folder operation status.") + .Build()), + }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/ByKeyMediaTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/ByKeyMediaTypeFolderController.cs new file mode 100644 index 0000000000..7508cdfcf8 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/ByKeyMediaTypeFolderController.cs @@ -0,0 +1,25 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Folder; + +[ApiVersion("1.0")] +public class ByKeyMediaTypeFolderController : MediaTypeFolderControllerBase +{ + public ByKeyMediaTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaTypeContainerService mediaTypeContainerService) + : base(backOfficeSecurityAccessor, mediaTypeContainerService) + { + } + + [HttpGet("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByKey(Guid id) => await GetFolderAsync(id); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/CreateMediaTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/CreateMediaTypeFolderController.cs new file mode 100644 index 0000000000..518b9071f8 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/CreateMediaTypeFolderController.cs @@ -0,0 +1,29 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Folder; + +[ApiVersion("1.0")] +public class CreateMediaTypeFolderController : MediaTypeFolderControllerBase +{ + public CreateMediaTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaTypeContainerService mediaTypeContainerService) + : base(backOfficeSecurityAccessor, mediaTypeContainerService) + { + } + + [HttpPost] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Create(CreateFolderRequestModel createFolderRequestModel) + => await CreateFolderAsync( + createFolderRequestModel, + controller => nameof(controller.ByKey)).ConfigureAwait(false); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/DeleteMediaTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/DeleteMediaTypeFolderController.cs new file mode 100644 index 0000000000..fe37aeab13 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/DeleteMediaTypeFolderController.cs @@ -0,0 +1,25 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Folder; + +[ApiVersion("1.0")] +public class DeleteMediaTypeFolderController : MediaTypeFolderControllerBase +{ + public DeleteMediaTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaTypeContainerService mediaTypeContainerService) + : base(backOfficeSecurityAccessor, mediaTypeContainerService) + { + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Delete(Guid id) => await DeleteFolderAsync(id); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/MediaTypeFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/MediaTypeFolderControllerBase.cs new file mode 100644 index 0000000000..142db0127b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/MediaTypeFolderControllerBase.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Folder; + +[ApiController] +[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MediaType}/folder")] +[ApiExplorerSettings(GroupName = "Media Type")] +[Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessMediaOrMediaTypes)] +public abstract class MediaTypeFolderControllerBase : FolderManagementControllerBase +{ + protected MediaTypeFolderControllerBase( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaTypeContainerService mediaTypeContainerService) + : base(backOfficeSecurityAccessor, mediaTypeContainerService) + { + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/UpdateMediaTypeFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/UpdateMediaTypeFolderController.cs new file mode 100644 index 0000000000..d5666ac47a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Folder/UpdateMediaTypeFolderController.cs @@ -0,0 +1,27 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Folder; + +[ApiVersion("1.0")] +public class UpdateMediaTypeFolderController : MediaTypeFolderControllerBase +{ + public UpdateMediaTypeFolderController( + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IMediaTypeContainerService mediaTypeContainerService) + : base(backOfficeSecurityAccessor, mediaTypeContainerService) + { + } + + [HttpPut("{id:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Update(Guid id, UpdateFolderResponseModel updateFolderResponseModel) + => await UpdateFolderAsync(id, updateFolderResponseModel); +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index b375ae00bb..1a1def8cc4 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -56,6 +56,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions AddPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); AddPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); AddPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); + AddPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Media, Constants.Applications.Settings); AddPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Members); AddPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); AddPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Security.AllowedApplicationsClaimType, Constants.Applications.Settings); diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 9ccd312a9d..a113ca23a5 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -996,6 +996,46 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } } }, "security": [ @@ -1102,6 +1142,26 @@ "200": { "description": "Success" }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, "404": { "description": "Not Found", "content": { @@ -1151,7 +1211,7 @@ "schema": { "oneOf": [ { - "$ref": "#/components/schemas/UpdateFolderReponseModel" + "$ref": "#/components/schemas/UpdateFolderResponseModel" } ] } @@ -1160,7 +1220,7 @@ "schema": { "oneOf": [ { - "$ref": "#/components/schemas/UpdateFolderReponseModel" + "$ref": "#/components/schemas/UpdateFolderResponseModel" } ] } @@ -1169,7 +1229,7 @@ "schema": { "oneOf": [ { - "$ref": "#/components/schemas/UpdateFolderReponseModel" + "$ref": "#/components/schemas/UpdateFolderResponseModel" } ] } @@ -1180,6 +1240,26 @@ "200": { "description": "Success" }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, "404": { "description": "Not Found", "content": { @@ -2795,6 +2875,348 @@ ] } }, + "/umbraco/management/api/v1/document-type/folder": { + "post": { + "tags": [ + "Document Type" + ], + "operationId": "PostDocumentTypeFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/document-type/folder/{id}": { + "get": { + "tags": [ + "Document Type" + ], + "operationId": "GetDocumentTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "delete": { + "tags": [ + "Document Type" + ], + "operationId": "DeleteDocumentTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "put": { + "tags": [ + "Document Type" + ], + "operationId": "PutDocumentTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/document-type/item": { "get": { "tags": [ @@ -6818,6 +7240,348 @@ ] } }, + "/umbraco/management/api/v1/media-type/folder": { + "post": { + "tags": [ + "Media Type" + ], + "operationId": "PostMediaTypeFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateFolderRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/media-type/folder/{id}": { + "get": { + "tags": [ + "Media Type" + ], + "operationId": "GetMediaTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/FolderResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "delete": { + "tags": [ + "Media Type" + ], + "operationId": "DeleteMediaTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "put": { + "tags": [ + "Media Type" + ], + "operationId": "PutMediaTypeFolderById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateFolderResponseModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, "/umbraco/management/api/v1/media-type/item": { "get": { "tags": [ @@ -21159,7 +21923,7 @@ ], "additionalProperties": false }, - "UpdateFolderReponseModel": { + "UpdateFolderResponseModel": { "type": "object", "allOf": [ { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderCreateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreateFolderRequestModel.cs similarity index 99% rename from src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderCreateModel.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreateFolderRequestModel.cs index c8d4b1eae0..23246e2503 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderCreateModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreateFolderRequestModel.cs @@ -3,5 +3,6 @@ public class CreateFolderRequestModel : FolderModelBase { public Guid? Id { get; set; } + public Guid? ParentId { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderUpdateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/UpdateFolderResponseModel.cs similarity index 52% rename from src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderUpdateModel.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/Folder/UpdateFolderResponseModel.cs index e4918e0ed3..7030e692fa 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderUpdateModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/UpdateFolderResponseModel.cs @@ -1,5 +1,5 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Folder; -public class UpdateFolderReponseModel : FolderModelBase +public class UpdateFolderResponseModel : FolderModelBase { } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 02811d1a2d..91db611c98 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -288,6 +288,8 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs index e1cdac61c5..69caeb8038 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IDataTypeContainerRepository.cs @@ -2,7 +2,4 @@ namespace Umbraco.Cms.Core.Persistence.Repositories; public interface IDataTypeContainerRepository : IEntityContainerRepository { - - bool HasDuplicateName(Guid parentKey, string name); - bool HasDuplicateName(int parentId, string name); } diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs index 33ee208b63..5b49ba0dc5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityContainerRepository.cs @@ -8,4 +8,7 @@ public interface IEntityContainerRepository : IReadRepository Get(string name, int level); + bool HasDuplicateName(Guid parentKey, string name); + + bool HasDuplicateName(int parentId, string name); } diff --git a/src/Umbraco.Core/Services/ContentTypeContainerService.cs b/src/Umbraco.Core/Services/ContentTypeContainerService.cs new file mode 100644 index 0000000000..a1bcc4da30 --- /dev/null +++ b/src/Umbraco.Core/Services/ContentTypeContainerService.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Core.Services; + +internal sealed class ContentTypeContainerService : EntityTypeContainerService, IContentTypeContainerService +{ + public ContentTypeContainerService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IDocumentTypeContainerRepository entityContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IUserIdKeyResolver userIdKeyResolver) + : base(provider, loggerFactory, eventMessagesFactory, entityContainerRepository, auditRepository, entityRepository, userIdKeyResolver) + { + } + + protected override Guid ContainedObjectType => Constants.ObjectTypes.DocumentType; + + protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.DocumentTypeContainer; +} diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index c6b840d4ac..6f2b2ddef5 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -959,6 +959,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe protected Guid ContainerObjectType => EntityContainer.GetContainerObjectType(ContainedObjectType); + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -999,6 +1000,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe } } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -1040,6 +1042,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return OperationResult.Attempt.Succeed(eventMessages); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public EntityContainer? GetContainer(int containerId) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -1048,6 +1051,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return _containerRepository.Get(containerId); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public EntityContainer? GetContainer(Guid containerId) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -1056,6 +1060,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return _containerRepository.Get(containerId); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public IEnumerable GetContainers(int[] containerIds) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -1064,6 +1069,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return _containerRepository.GetMany(containerIds); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public IEnumerable GetContainers(TItem item) { var ancestorIds = item.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) @@ -1074,6 +1080,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return GetContainers(ancestorIds); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public IEnumerable GetContainers(string name, int level) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); @@ -1082,6 +1089,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe return _containerRepository.Get(name, level); } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); @@ -1121,6 +1129,7 @@ public abstract class ContentTypeServiceBase : ContentTypeSe // TODO: Audit trail ? } + [Obsolete($"Please use {nameof(IContentTypeContainerService)} or {nameof(IMediaTypeContainerService)} for all content or media type container operations. Will be removed in V16.")] public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) { EventMessages eventMessages = EventMessagesFactory.Get(); diff --git a/src/Umbraco.Core/Services/DataTypeContainerService.cs b/src/Umbraco.Core/Services/DataTypeContainerService.cs index 29ae1a1dee..237aa00aec 100644 --- a/src/Umbraco.Core/Services/DataTypeContainerService.cs +++ b/src/Umbraco.Core/Services/DataTypeContainerService.cs @@ -1,197 +1,26 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Models.Entities; -using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; -internal sealed class DataTypeContainerService : RepositoryService, IDataTypeContainerService +internal sealed class DataTypeContainerService : EntityTypeContainerService, IDataTypeContainerService { - private readonly IDataTypeContainerRepository _dataTypeContainerRepository; - private readonly IAuditRepository _auditRepository; - private readonly IEntityRepository _entityRepository; - private readonly IUserIdKeyResolver _userIdKeyResolver; - public DataTypeContainerService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IDataTypeContainerRepository dataTypeContainerRepository, + IDataTypeContainerRepository entityContainerRepository, IAuditRepository auditRepository, IEntityRepository entityRepository, IUserIdKeyResolver userIdKeyResolver) - : base(provider, loggerFactory, eventMessagesFactory) + : base(provider, loggerFactory, eventMessagesFactory, entityContainerRepository, auditRepository, entityRepository, userIdKeyResolver) { - _dataTypeContainerRepository = dataTypeContainerRepository; - _auditRepository = auditRepository; - _entityRepository = entityRepository; - _userIdKeyResolver = userIdKeyResolver; } - /// - public async Task GetAsync(Guid id) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Task.FromResult(_dataTypeContainerRepository.Get(id)); - } + protected override Guid ContainedObjectType => Constants.ObjectTypes.DataType; - /// - public async Task GetParentAsync(EntityContainer container) - => await Task.FromResult(GetParent(container)); - - /// - public async Task GetParentAsync(IDataType dataType) - => await Task.FromResult(GetParent(dataType)); - - /// - public async Task> CreateAsync(EntityContainer container, Guid? parentKey, Guid userKey) - => await SaveAsync( - container, - userKey, - () => - { - if (container.Id > 0) - { - return DataTypeContainerOperationStatus.InvalidId; - } - - if (_dataTypeContainerRepository.Get(container.Key) is not null) - { - return DataTypeContainerOperationStatus.DuplicateKey; - } - - EntityContainer? parentContainer = parentKey.HasValue - ? _dataTypeContainerRepository.Get(parentKey.Value) - : null; - - if (parentKey.HasValue && parentContainer == null) - { - return DataTypeContainerOperationStatus.ParentNotFound; - } - - - - if (_dataTypeContainerRepository.HasDuplicateName(container.ParentId, container.Name!)) - { - return DataTypeContainerOperationStatus.DuplicateName; - } - - container.ParentId = parentContainer?.Id ?? Constants.System.Root; - return DataTypeContainerOperationStatus.Success; - }, - AuditType.New); - - /// - public async Task> UpdateAsync(EntityContainer container, Guid userKey) - => await SaveAsync( - container, - userKey, - () => - { - if (container.Id == 0) - { - return DataTypeContainerOperationStatus.InvalidId; - } - - if (container.IsPropertyDirty(nameof(EntityContainer.ParentId))) - { - LoggerFactory.CreateLogger().LogWarning($"Cannot use {nameof(UpdateAsync)} to change the container parent. Move the container instead."); - return DataTypeContainerOperationStatus.ParentNotFound; - } - - return DataTypeContainerOperationStatus.Success; - }, - AuditType.New); - - /// - public async Task> DeleteAsync(Guid id, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - EntityContainer? container = _dataTypeContainerRepository.Get(id); - if (container == null) - { - return Attempt.FailWithStatus(DataTypeContainerOperationStatus.NotFound, null); - } - - // 'container' here does not know about its children, so we need - // to get it again from the entity repository, as a light entity - IEntitySlim? entity = _entityRepository.Get(container.Id); - if (entity?.HasChildren is true) - { - scope.Complete(); - return Attempt.FailWithStatus(DataTypeContainerOperationStatus.NotEmpty, container); - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - - var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(deletingEntityContainerNotification)) - { - scope.Complete(); - return Attempt.FailWithStatus(DataTypeContainerOperationStatus.CancelledByNotification, container); - } - - _dataTypeContainerRepository.Delete(container); - - var currentUserId = await _userIdKeyResolver.GetAsync(userKey); - Audit(AuditType.Delete, currentUserId, container.Id); - scope.Complete(); - - scope.Notifications.Publish(new EntityContainerDeletedNotification(container, eventMessages).WithStateFrom(deletingEntityContainerNotification)); - - return Attempt.SucceedWithStatus(DataTypeContainerOperationStatus.Success, container); - } - - private async Task> SaveAsync(EntityContainer container, Guid userKey, Func operationValidation, AuditType auditType) - { - if (container.ContainedObjectType != Constants.ObjectTypes.DataType) - { - return Attempt.FailWithStatus(DataTypeContainerOperationStatus.InvalidObjectType, container); - } - - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - DataTypeContainerOperationStatus operationValidationStatus = operationValidation(); - if (operationValidationStatus != DataTypeContainerOperationStatus.Success) - { - return Attempt.FailWithStatus(operationValidationStatus, container); - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingEntityContainerNotification = new EntityContainerSavingNotification(container, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingEntityContainerNotification)) - { - scope.Complete(); - return Attempt.FailWithStatus(DataTypeContainerOperationStatus.CancelledByNotification, container); - } - - _dataTypeContainerRepository.Save(container); - - var currentUserId = await _userIdKeyResolver.GetAsync(userKey); - Audit(auditType, currentUserId, container.Id); - scope.Complete(); - - scope.Notifications.Publish(new EntityContainerSavedNotification(container, eventMessages).WithStateFrom(savingEntityContainerNotification)); - - return Attempt.SucceedWithStatus(DataTypeContainerOperationStatus.Success, container); - } - - private EntityContainer? GetParent(ITreeEntity treeEntity) - { - if (treeEntity.ParentId == Constants.System.Root) - { - return null; - } - - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return _dataTypeContainerRepository.Get(treeEntity.ParentId); - } - - private void Audit(AuditType type, int userId, int objectId) - => _auditRepository.Save(new AuditItem(objectId, type, userId, UmbracoObjectTypes.DataTypeContainer.GetName())); + protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.DataTypeContainer; } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index fcb401640b..99d8c225c3 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -91,7 +91,7 @@ namespace Umbraco.Cms.Core.Services.Implement #region Containers - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V15.")] public Attempt?> CreateContainer(int parentId, Guid key, string name, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -100,23 +100,14 @@ namespace Umbraco.Cms.Core.Services.Implement try { Guid? parentKey = parentId > 0 ? _dataTypeContainerRepository.Get(parentId)?.Key : null; - - var container = new EntityContainer(Constants.ObjectTypes.DataType) - { - Name = name, - ParentId = parentId, - CreatorId = userId, - Key = key - }; - Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - Attempt result = _dataTypeContainerService.CreateAsync(container, parentKey, currentUserKey).GetAwaiter().GetResult(); + Attempt result = _dataTypeContainerService.CreateAsync(key, name, parentKey, currentUserKey).GetAwaiter().GetResult(); // mimic old service behavior return result.Status switch { - DataTypeContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs, container), - DataTypeContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs, container), + EntityContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs, new EntityContainer(Constants.ObjectTypes.DataType)), + EntityContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs, result.Result ?? throw new NullReferenceException("Container creation operation succeeded but the result was null")), _ => throw new InvalidOperationException($"Invalid operation status: {result.Status}") }; } @@ -127,25 +118,25 @@ namespace Umbraco.Cms.Core.Services.Implement } } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public EntityContainer? GetContainer(int containerId) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); return _dataTypeContainerRepository.Get(containerId); } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public EntityContainer? GetContainer(Guid containerId) => _dataTypeContainerService.GetAsync(containerId).GetAwaiter().GetResult(); - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public IEnumerable GetContainers(string name, int level) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); return _dataTypeContainerRepository.Get(name, level); } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public IEnumerable GetContainers(IDataType dataType) { var ancestorIds = dataType.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries) @@ -160,14 +151,14 @@ namespace Umbraco.Cms.Core.Services.Implement return GetContainers(ancestorIds); } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public IEnumerable GetContainers(int[] containerIds) { using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); return _dataTypeContainerRepository.GetMany(containerIds); } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public Attempt SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -177,23 +168,23 @@ namespace Umbraco.Cms.Core.Services.Implement Guid? parentKey = isNew && container.ParentId > 0 ? _dataTypeContainerRepository.Get(container.ParentId)?.Key : null; Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - Attempt result = isNew - ? _dataTypeContainerService.CreateAsync(container, parentKey, currentUserKey).GetAwaiter().GetResult() - : _dataTypeContainerService.UpdateAsync(container, currentUserKey).GetAwaiter().GetResult(); + Attempt result = isNew + ? _dataTypeContainerService.CreateAsync(container.Key, container.Name.IfNullOrWhiteSpace(string.Empty), parentKey, currentUserKey).GetAwaiter().GetResult() + : _dataTypeContainerService.UpdateAsync(container.Key, container.Name.IfNullOrWhiteSpace(string.Empty), currentUserKey).GetAwaiter().GetResult(); // mimic old service behavior return result.Status switch { - DataTypeContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs), - DataTypeContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs), - DataTypeContainerOperationStatus.ParentNotFound => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.")), - DataTypeContainerOperationStatus.InvalidObjectType => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container.")), + EntityContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs), + EntityContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs), + EntityContainerOperationStatus.ParentNotFound => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException("Cannot save a container with a modified parent, move the container instead.")), + EntityContainerOperationStatus.InvalidObjectType => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException("Not a " + Constants.ObjectTypes.DataType + " container.")), _ => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException($"Invalid operation status: {result.Status}")) }; } } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public Attempt DeleteContainer(int containerId, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -206,19 +197,19 @@ namespace Umbraco.Cms.Core.Services.Implement } Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - Attempt result = _dataTypeContainerService.DeleteAsync(container.Key, currentUserKey).GetAwaiter().GetResult(); + Attempt result = _dataTypeContainerService.DeleteAsync(container.Key, currentUserKey).GetAwaiter().GetResult(); // mimic old service behavior return result.Status switch { - DataTypeContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs), - DataTypeContainerOperationStatus.NotEmpty => Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs)), - DataTypeContainerOperationStatus.CancelledByNotification => Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs)), + EntityContainerOperationStatus.Success => OperationResult.Attempt.Succeed(evtMsgs), + EntityContainerOperationStatus.NotEmpty => Attempt.Fail(new OperationResult(OperationResultType.FailedCannot, evtMsgs)), + EntityContainerOperationStatus.CancelledByNotification => Attempt.Fail(new OperationResult(OperationResultType.FailedCancelledByEvent, evtMsgs)), _ => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException($"Invalid operation status: {result.Status}")) }; } } - [Obsolete("Please use IDataTypeContainerService for all data type container operations. Will be removed in V15.")] + [Obsolete($"Please use {nameof(IDataTypeContainerService)} for all data type container operations. Will be removed in V16.")] public Attempt?> RenameContainer(int id, string name, int userId = Constants.Security.SuperUserId) { EventMessages evtMsgs = EventMessagesFactory.Get(); @@ -236,12 +227,12 @@ namespace Umbraco.Cms.Core.Services.Implement container.Name = name; Guid currentUserKey = _userIdKeyResolver.GetAsync(userId).GetAwaiter().GetResult(); - Attempt result = _dataTypeContainerService.UpdateAsync(container, currentUserKey).GetAwaiter().GetResult(); + Attempt result = _dataTypeContainerService.UpdateAsync(container.Key, container.Name, currentUserKey).GetAwaiter().GetResult(); // mimic old service behavior return result.Status switch { - DataTypeContainerOperationStatus.Success => OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, container), - DataTypeContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs, container), + EntityContainerOperationStatus.Success => OperationResult.Attempt.Succeed(OperationResultType.Success, evtMsgs, result.Result ?? throw new NullReferenceException("Container update operation succeeded but the result was null")), + EntityContainerOperationStatus.CancelledByNotification => OperationResult.Attempt.Cancel(evtMsgs, container), _ => OperationResult.Attempt.Fail(evtMsgs, new InvalidOperationException($"Invalid operation status: {result.Status}")) }; } diff --git a/src/Umbraco.Core/Services/EntityTypeContainerService.cs b/src/Umbraco.Core/Services/EntityTypeContainerService.cs new file mode 100644 index 0000000000..9524d9b3db --- /dev/null +++ b/src/Umbraco.Core/Services/EntityTypeContainerService.cs @@ -0,0 +1,220 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +internal abstract class EntityTypeContainerService : RepositoryService, IEntityTypeContainerService + where TTreeEntity : ITreeEntity + where TEntityContainerRepository : IEntityContainerRepository +{ + private readonly TEntityContainerRepository _entityContainerRepository; + private readonly IAuditRepository _auditRepository; + private readonly IEntityRepository _entityRepository; + private readonly IUserIdKeyResolver _userIdKeyResolver; + + protected abstract Guid ContainedObjectType { get; } + + protected abstract UmbracoObjectTypes ContainerObjectType { get; } + + protected EntityTypeContainerService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + TEntityContainerRepository entityContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IUserIdKeyResolver userIdKeyResolver) + : base(provider, loggerFactory, eventMessagesFactory) + { + _entityContainerRepository = entityContainerRepository; + _auditRepository = auditRepository; + _entityRepository = entityRepository; + _userIdKeyResolver = userIdKeyResolver; + } + + /// + public async Task GetAsync(Guid id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return await Task.FromResult(_entityContainerRepository.Get(id)); + } + + /// + public async Task GetParentAsync(EntityContainer container) + => await Task.FromResult(GetParent(container)); + + /// + public async Task GetParentAsync(TTreeEntity entity) + => await Task.FromResult(GetParent(entity)); + + /// + public async Task> CreateAsync(Guid? key, string name, Guid? parentKey, Guid userKey) + { + var container = new EntityContainer(ContainedObjectType) { Name = name }; + if (key.HasValue) + { + container.Key = key.Value; + } + + return await SaveAsync( + container, + userKey, + () => + { + if (container.Id > 0) + { + return EntityContainerOperationStatus.InvalidId; + } + + if (_entityContainerRepository.Get(container.Key) is not null) + { + return EntityContainerOperationStatus.DuplicateKey; + } + + EntityContainer? parentContainer = parentKey.HasValue + ? _entityContainerRepository.Get(parentKey.Value) + : null; + + if (parentKey.HasValue && parentContainer == null) + { + return EntityContainerOperationStatus.ParentNotFound; + } + + if (_entityContainerRepository.HasDuplicateName(container.ParentId, container.Name!)) + { + return EntityContainerOperationStatus.DuplicateName; + } + + container.ParentId = parentContainer?.Id ?? Constants.System.Root; + return EntityContainerOperationStatus.Success; + }, + AuditType.New); + } + + /// + public async Task> UpdateAsync(Guid key, string name, Guid userKey) + { + EntityContainer? container = await GetAsync(key); + if (container is null) + { + return Attempt.FailWithStatus(EntityContainerOperationStatus.NotFound, container); + } + + container.Name = name; + + return await SaveAsync( + container, + userKey, + () => + { + if (container.Id == 0) + { + return EntityContainerOperationStatus.InvalidId; + } + + if (container.IsPropertyDirty(nameof(EntityContainer.ParentId))) + { + LoggerFactory.CreateLogger().LogWarning( + $"Cannot use {nameof(UpdateAsync)} to change the container parent. Move the container instead."); + return EntityContainerOperationStatus.ParentNotFound; + } + + return EntityContainerOperationStatus.Success; + }, + AuditType.New); + } + + /// + public async Task> DeleteAsync(Guid id, Guid userKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + EntityContainer? container = _entityContainerRepository.Get(id); + if (container == null) + { + return Attempt.FailWithStatus(EntityContainerOperationStatus.NotFound, null); + } + + // 'container' here does not know about its children, so we need + // to get it again from the entity repository, as a light entity + IEntitySlim? entity = _entityRepository.Get(container.Id); + if (entity?.HasChildren is true) + { + scope.Complete(); + return Attempt.FailWithStatus(EntityContainerOperationStatus.NotEmpty, container); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + + var deletingEntityContainerNotification = new EntityContainerDeletingNotification(container, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(deletingEntityContainerNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(EntityContainerOperationStatus.CancelledByNotification, container); + } + + _entityContainerRepository.Delete(container); + + var currentUserId = await _userIdKeyResolver.GetAsync(userKey); + Audit(AuditType.Delete, currentUserId, container.Id); + scope.Complete(); + + scope.Notifications.Publish(new EntityContainerDeletedNotification(container, eventMessages).WithStateFrom(deletingEntityContainerNotification)); + + return Attempt.SucceedWithStatus(EntityContainerOperationStatus.Success, container); + } + + private async Task> SaveAsync(EntityContainer container, Guid userKey, Func operationValidation, AuditType auditType) + { + if (container.ContainedObjectType != ContainedObjectType) + { + return Attempt.FailWithStatus(EntityContainerOperationStatus.InvalidObjectType, container); + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + EntityContainerOperationStatus operationValidationStatus = operationValidation(); + if (operationValidationStatus != EntityContainerOperationStatus.Success) + { + return Attempt.FailWithStatus(operationValidationStatus, container); + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + var savingEntityContainerNotification = new EntityContainerSavingNotification(container, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingEntityContainerNotification)) + { + scope.Complete(); + return Attempt.FailWithStatus(EntityContainerOperationStatus.CancelledByNotification, container); + } + + _entityContainerRepository.Save(container); + + var currentUserId = await _userIdKeyResolver.GetAsync(userKey); + Audit(auditType, currentUserId, container.Id); + scope.Complete(); + + scope.Notifications.Publish(new EntityContainerSavedNotification(container, eventMessages).WithStateFrom(savingEntityContainerNotification)); + + return Attempt.SucceedWithStatus(EntityContainerOperationStatus.Success, container); + } + + private EntityContainer? GetParent(ITreeEntity treeEntity) + { + if (treeEntity.ParentId == Constants.System.Root) + { + return null; + } + + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + return _entityContainerRepository.Get(treeEntity.ParentId); + } + + private void Audit(AuditType type, int userId, int objectId) + => _auditRepository.Save(new AuditItem(objectId, type, userId, ContainerObjectType.GetName())); +} diff --git a/src/Umbraco.Core/Services/IContentTypeContainerService.cs b/src/Umbraco.Core/Services/IContentTypeContainerService.cs new file mode 100644 index 0000000000..9f92581d4c --- /dev/null +++ b/src/Umbraco.Core/Services/IContentTypeContainerService.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services; + +public interface IContentTypeContainerService : IEntityTypeContainerService +{ +} diff --git a/src/Umbraco.Core/Services/IDataTypeContainerService.cs b/src/Umbraco.Core/Services/IDataTypeContainerService.cs index 6f5220583d..b26b44bcc8 100644 --- a/src/Umbraco.Core/Services/IDataTypeContainerService.cs +++ b/src/Umbraco.Core/Services/IDataTypeContainerService.cs @@ -1,47 +1,7 @@ using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; -public interface IDataTypeContainerService +public interface IDataTypeContainerService : IEntityTypeContainerService { - /// - /// Gets a data type container - /// - /// The ID of the data type container to get. - /// - Task GetAsync(Guid id); - - /// - /// Gets the parent container of a data type container - /// - /// The container whose parent container to get. - /// - Task GetParentAsync(EntityContainer container); - - /// - /// Creates a new data type container - /// - /// The container to create. - /// The ID of the parent container to create the new container under. - /// Key of the user issuing the creation. - /// - /// If parent key is supplied as null, the container will be created at the data type tree root. - Task> CreateAsync(EntityContainer container, Guid? parentKey, Guid userKey); - - /// - /// Updates an existing data type container - /// - /// The container to create. - /// Key of the user issuing the update. - /// - Task> UpdateAsync(EntityContainer container, Guid userKey); - - /// - /// Deletes a data type container - /// - /// The ID of the container to delete. - /// Key of the user issuing the deletion. - /// - Task> DeleteAsync(Guid id, Guid userKey); } diff --git a/src/Umbraco.Core/Services/IEntityTypeContainerService.cs b/src/Umbraco.Core/Services/IEntityTypeContainerService.cs new file mode 100644 index 0000000000..e58e322304 --- /dev/null +++ b/src/Umbraco.Core/Services/IEntityTypeContainerService.cs @@ -0,0 +1,58 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface IEntityTypeContainerService + where TTreeEntity : ITreeEntity +{ + /// + /// Gets a container + /// + /// The ID of the container to get. + /// + Task GetAsync(Guid id); + + /// + /// Gets the parent container of a container + /// + /// The container whose parent container to get. + /// + Task GetParentAsync(EntityContainer container); + + /// + /// Gets the parent container of an entity + /// + /// The entity whose parent container to get. + /// + Task GetParentAsync(TTreeEntity entity); + + /// + /// Creates a new container + /// + /// The key to assign to the newly created container (if null is specified, a random key will be assigned). + /// The name of the created container. + /// The ID of the parent container to create the new container under. + /// Key of the user issuing the creation. + /// + /// If parent key is supplied as null, the container will be created at the tree root. + Task> CreateAsync(Guid? key, string name, Guid? parentKey, Guid userKey); + + /// + /// Updates an existing container + /// + /// The key of the container to update. + /// The name to assign to the container. + /// Key of the user issuing the update. + /// + Task> UpdateAsync(Guid key, string name, Guid userKey); + + /// + /// Deletes a container + /// + /// The ID of the container to delete. + /// Key of the user issuing the deletion. + /// + Task> DeleteAsync(Guid id, Guid userKey); +} diff --git a/src/Umbraco.Core/Services/IMediaTypeContainerService.cs b/src/Umbraco.Core/Services/IMediaTypeContainerService.cs new file mode 100644 index 0000000000..7d0f955bd8 --- /dev/null +++ b/src/Umbraco.Core/Services/IMediaTypeContainerService.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Services; + +public interface IMediaTypeContainerService : IEntityTypeContainerService +{ +} diff --git a/src/Umbraco.Core/Services/MediaTypeContainerService.cs b/src/Umbraco.Core/Services/MediaTypeContainerService.cs new file mode 100644 index 0000000000..28ae8ba2a9 --- /dev/null +++ b/src/Umbraco.Core/Services/MediaTypeContainerService.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Core.Services; + +internal sealed class MediaTypeContainerService : EntityTypeContainerService, IMediaTypeContainerService +{ + public MediaTypeContainerService( + ICoreScopeProvider provider, + ILoggerFactory loggerFactory, + IEventMessagesFactory eventMessagesFactory, + IMediaTypeContainerRepository entityContainerRepository, + IAuditRepository auditRepository, + IEntityRepository entityRepository, + IUserIdKeyResolver userIdKeyResolver) + : base(provider, loggerFactory, eventMessagesFactory, entityContainerRepository, auditRepository, entityRepository, userIdKeyResolver) + { + } + + protected override Guid ContainedObjectType => Constants.ObjectTypes.MediaType; + + protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MediaTypeContainer; +} diff --git a/src/Umbraco.Core/Services/OperationStatus/DataTypeContainerOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/EntityContainerOperationStatus.cs similarity index 83% rename from src/Umbraco.Core/Services/OperationStatus/DataTypeContainerOperationStatus.cs rename to src/Umbraco.Core/Services/OperationStatus/EntityContainerOperationStatus.cs index e5f9e5e1ec..1d58356448 100644 --- a/src/Umbraco.Core/Services/OperationStatus/DataTypeContainerOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/EntityContainerOperationStatus.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Core.Services.OperationStatus; -public enum DataTypeContainerOperationStatus +public enum EntityContainerOperationStatus { Success, CancelledByNotification, diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs index dbde4e1f5b..e781521a77 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeContainerRepository.cs @@ -21,6 +21,10 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement public IEnumerable Get(string name, int level) => Enumerable.Empty(); + public bool HasDuplicateName(Guid parentKey, string name) => false; + + public bool HasDuplicateName(int parentId, string name) => false; + public EntityContainer? Get(int id) => null; public IEnumerable GetMany(params int[]? ids) => Enumerable.Empty(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeContainerServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeContainerServiceTests.cs new file mode 100644 index 0000000000..1a5f7a2dad --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeContainerServiceTests.cs @@ -0,0 +1,204 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +/// +/// Tests covering the ContentTypeContainerService +/// +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ContentTypeContainerServiceTests : UmbracoIntegrationTest +{ + private IContentTypeContainerService ContentTypeContainerService => GetRequiredService(); + + private IContentTypeService ContentTypeService => GetRequiredService(); + + [Test] + public async Task Can_Create_Container_At_Root() + { + var result = await ContentTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var created = await ContentTypeContainerService.GetAsync(result.Result.Key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Create_Child_Container() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; + + var result = await ContentTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var created = await ContentTypeContainerService.GetAsync(result.Result.Key); + Assert.NotNull(created); + Assert.AreEqual("Child Container", created.Name); + Assert.AreEqual(root.Id, created.ParentId); + } + + [Test] + public async Task Can_Create_Container_With_Explicit_Key() + { + var key = Guid.NewGuid(); + var result = await ContentTypeContainerService.CreateAsync(key, "Root Container", null, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + Assert.AreEqual(key, result.Result.Key); + + var created = await ContentTypeContainerService.GetAsync(key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Update_Container_At_Root() + { + var key = (await ContentTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result.Key; + + var result = await ContentTypeContainerService.UpdateAsync(key, "Root Container UPDATED", Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var updated = await ContentTypeContainerService.GetAsync(key); + Assert.NotNull(updated); + Assert.AreEqual("Root Container UPDATED", updated.Name); + Assert.AreEqual(Constants.System.Root, updated.ParentId); + } + + [Test] + public async Task Can_Update_Child_Container() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await ContentTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await ContentTypeContainerService.UpdateAsync(child.Key, "Child Container UPDATED", Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + EntityContainer updated = await ContentTypeContainerService.GetAsync(child.Key); + Assert.NotNull(updated); + Assert.AreEqual("Child Container UPDATED", updated.Name); + Assert.AreEqual(root.Id, updated.ParentId); + } + + [Test] + public async Task Can_Get_Container_At_Root() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + EntityContainer created = await ContentTypeContainerService.GetAsync(root.Key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Get_Child_Container() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await ContentTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + EntityContainer created = await ContentTypeContainerService.GetAsync(child.Key); + Assert.IsNotNull(created); + Assert.AreEqual("Child Container", created.Name); + Assert.AreEqual(root.Id, child.ParentId); + } + + [Test] + public async Task Can_Delete_Container_At_Root() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + var result = await ContentTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var current = await ContentTypeContainerService.GetAsync(root.Key); + Assert.IsNull(current); + } + + [Test] + public async Task Can_Delete_Child_Container() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await ContentTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await ContentTypeContainerService.DeleteAsync(child.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + child = await ContentTypeContainerService.GetAsync(child.Key); + Assert.IsNull(child); + + root = await ContentTypeContainerService.GetAsync(root.Key); + Assert.IsNotNull(root); + } + + [Test] + public async Task Cannot_Create_Child_Container_Below_Invalid_Parent() + { + var key = Guid.NewGuid(); + var result = await ContentTypeContainerService.CreateAsync(key, "Child Container", Guid.NewGuid(), Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.ParentNotFound, result.Status); + + var created = await ContentTypeContainerService.GetAsync(key); + Assert.IsNull(created); + } + + [Test] + public async Task Cannot_Delete_Container_With_Child_Container() + { + EntityContainer root = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await ContentTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await ContentTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); + + var current = await ContentTypeContainerService.GetAsync(root.Key); + Assert.IsNotNull(current); + } + + [Test] + public async Task Cannot_Delete_Container_With_Child_ContentType() + { + EntityContainer container = (await ContentTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + IContentType contentType = new ContentType(ShortStringHelper, container.Id) + { + Alias = "test", Name = "Test" + }; + ContentTypeService.Save(contentType); + + var result = await ContentTypeContainerService.DeleteAsync(container.Key, Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); + + var currentContainer = await ContentTypeContainerService.GetAsync(container.Key); + Assert.IsNotNull(currentContainer); + + var currentContentType = ContentTypeService.Get(contentType.Key); + Assert.IsNotNull(currentContentType); + } + + [Test] + public async Task Cannot_Delete_Non_Existing_Container() + { + var result = await ContentTypeContainerService.DeleteAsync(Guid.NewGuid(), Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotFound, result.Status); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeContainerServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeContainerServiceTests.cs index dbb7012d89..f05daf58df 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeContainerServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeContainerServiceTests.cs @@ -31,13 +31,11 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Create_Container_At_Root() { - EntityContainer toCreate = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - - var result = await DataTypeContainerService.CreateAsync(toCreate, null, Constants.Security.SuperUserKey); + var result = await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); - var created = await DataTypeContainerService.GetAsync(toCreate.Key); + var created = await DataTypeContainerService.GetAsync(result.Result.Key); Assert.NotNull(created); Assert.AreEqual("Root Container", created.Name); Assert.AreEqual(Constants.System.Root, created.ParentId); @@ -46,35 +44,43 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Create_Child_Container() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - var result = await DataTypeContainerService.CreateAsync(child, root.Key, Constants.Security.SuperUserKey); + var result = await DataTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); - var created = await DataTypeContainerService.GetAsync(child.Key); + var created = await DataTypeContainerService.GetAsync(result.Result.Key); Assert.NotNull(created); Assert.AreEqual("Child Container", created.Name); - Assert.AreEqual(root.Id, child.ParentId); + Assert.AreEqual(root.Id, created.ParentId); + } + + [Test] + public async Task Can_Create_Container_With_Explicit_Key() + { + var key = Guid.NewGuid(); + var result = await DataTypeContainerService.CreateAsync(key, "Root Container", null, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + Assert.AreEqual(key, result.Result.Key); + + var created = await DataTypeContainerService.GetAsync(key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); } [Test] public async Task Can_Update_Container_At_Root() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + var key = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result.Key; - EntityContainer toUpdate = await DataTypeContainerService.GetAsync(root.Key); - Assert.NotNull(toUpdate); - - toUpdate.Name += " UPDATED"; - var result = await DataTypeContainerService.UpdateAsync(toUpdate, Constants.Security.SuperUserKey); + var result = await DataTypeContainerService.UpdateAsync(key, "Root Container UPDATED", Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); - var updated = await DataTypeContainerService.GetAsync(toUpdate.Key); + var updated = await DataTypeContainerService.GetAsync(key); Assert.NotNull(updated); Assert.AreEqual("Root Container UPDATED", updated.Name); Assert.AreEqual(Constants.System.Root, updated.ParentId); @@ -83,21 +89,14 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Update_Child_Container() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await DataTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - await DataTypeContainerService.CreateAsync(child, root.Key, Constants.Security.SuperUserKey); - - EntityContainer toUpdate = await DataTypeContainerService.GetAsync(child.Key); - Assert.NotNull(toUpdate); - - toUpdate.Name += " UPDATED"; - var result = await DataTypeContainerService.UpdateAsync(toUpdate, Constants.Security.SuperUserKey); + var result = await DataTypeContainerService.UpdateAsync(child.Key, "Child Container UPDATED", Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); - var updated = await DataTypeContainerService.GetAsync(toUpdate.Key); + EntityContainer updated = await DataTypeContainerService.GetAsync(child.Key); Assert.NotNull(updated); Assert.AreEqual("Child Container UPDATED", updated.Name); Assert.AreEqual(root.Id, updated.ParentId); @@ -106,10 +105,9 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Get_Container_At_Root() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; - var created = await DataTypeContainerService.GetAsync(root.Key); + EntityContainer created = await DataTypeContainerService.GetAsync(root.Key); Assert.NotNull(created); Assert.AreEqual("Root Container", created.Name); Assert.AreEqual(Constants.System.Root, created.ParentId); @@ -118,13 +116,10 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Get_Child_Container() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await DataTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - await DataTypeContainerService.CreateAsync(child, root.Key, Constants.Security.SuperUserKey); - - var created = await DataTypeContainerService.GetAsync(child.Key); + EntityContainer created = await DataTypeContainerService.GetAsync(child.Key); Assert.IsNotNull(created); Assert.AreEqual("Child Container", created.Name); Assert.AreEqual(root.Id, child.ParentId); @@ -133,12 +128,11 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Delete_Container_At_Root() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; var result = await DataTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); var current = await DataTypeContainerService.GetAsync(root.Key); Assert.IsNull(current); @@ -147,100 +141,41 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Delete_Child_Container() { - Guid userKey = Constants.Security.SuperUserKey; - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, userKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await DataTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - await DataTypeContainerService.CreateAsync(child, root.Key, userKey); - - var result = await DataTypeContainerService.DeleteAsync(child.Key, userKey); + var result = await DataTypeContainerService.DeleteAsync(child.Key, Constants.Security.SuperUserKey); Assert.IsTrue(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.Success, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); - var current = await DataTypeContainerService.GetAsync(child.Key); - Assert.IsNull(current); + child = await DataTypeContainerService.GetAsync(child.Key); + Assert.IsNull(child); - current = await DataTypeContainerService.GetAsync(root.Key); - Assert.IsNotNull(current); + root = await DataTypeContainerService.GetAsync(root.Key); + Assert.IsNotNull(root); } [Test] public async Task Cannot_Create_Child_Container_Below_Invalid_Parent() { - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - var result = await DataTypeContainerService.CreateAsync(child, Guid.NewGuid(), Constants.Security.SuperUserKey); + var key = Guid.NewGuid(); + var result = await DataTypeContainerService.CreateAsync(key, "Child Container", Guid.NewGuid(), Constants.Security.SuperUserKey); Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.ParentNotFound, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.ParentNotFound, result.Status); - var created = await DataTypeContainerService.GetAsync(child.Key); + var created = await DataTypeContainerService.GetAsync(key); Assert.IsNull(created); } - [Test] - public async Task Cannot_Create_Child_Container_With_Explicit_Id() - { - EntityContainer toCreate = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container", Id = 1234 }; - - var result = await DataTypeContainerService.CreateAsync(toCreate, null, Constants.Security.SuperUserKey); - Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.InvalidId, result.Status); - - var created = await DataTypeContainerService.GetAsync(toCreate.Key); - Assert.IsNull(created); - } - - [TestCase(Constants.ObjectTypes.Strings.DocumentType)] - [TestCase(Constants.ObjectTypes.Strings.MediaType)] - public async Task Cannot_Create_Container_With_Invalid_Contained_Type(string containedObjectType) - { - EntityContainer toCreate = new EntityContainer(new Guid(containedObjectType)) { Name = "Root Container" }; - - var result = await DataTypeContainerService.CreateAsync(toCreate, null, Constants.Security.SuperUserKey); - Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.InvalidObjectType, result.Status); - - var created = await DataTypeContainerService.GetAsync(toCreate.Key); - Assert.IsNull(created); - } - - [Test] - public async Task Cannot_Update_Container_Parent() - { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); - - EntityContainer root2 = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 2" }; - await DataTypeContainerService.CreateAsync(root2, null, Constants.Security.SuperUserKey); - - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - await DataTypeContainerService.CreateAsync(child, root.Key, Constants.Security.SuperUserKey); - - EntityContainer toUpdate = await DataTypeContainerService.GetAsync(child.Key); - Assert.IsNotNull(toUpdate); - - toUpdate.ParentId = root2.Id; - var result = await DataTypeContainerService.UpdateAsync(toUpdate, Constants.Security.SuperUserKey); - Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.ParentNotFound, result.Status); - - var current = await DataTypeContainerService.GetAsync(child.Key); - Assert.IsNotNull(current); - Assert.AreEqual(root.Id, child.ParentId); - } - [Test] public async Task Cannot_Delete_Container_With_Child_Container() { - EntityContainer root = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(root, null, Constants.Security.SuperUserKey); - - EntityContainer child = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Child Container" }; - await DataTypeContainerService.CreateAsync(child, root.Key, Constants.Security.SuperUserKey); + EntityContainer root = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await DataTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; var result = await DataTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.NotEmpty, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); var current = await DataTypeContainerService.GetAsync(root.Key); Assert.IsNotNull(current); @@ -249,8 +184,7 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest [Test] public async Task Cannot_Delete_Container_With_Child_DataType() { - EntityContainer container = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }; - await DataTypeContainerService.CreateAsync(container, null, Constants.Security.SuperUserKey); + EntityContainer container = (await DataTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; IDataType dataType = new DataType(new TextboxPropertyEditor(DataValueEditorFactory, IOHelper, EditorConfigurationParser), ConfigurationEditorJsonSerializer) @@ -263,7 +197,7 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest var result = await DataTypeContainerService.DeleteAsync(container.Key, Constants.Security.SuperUserKey); Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.NotEmpty, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); var currentContainer = await DataTypeContainerService.GetAsync(container.Key); Assert.IsNotNull(currentContainer); @@ -277,6 +211,6 @@ public class DataTypeContainerServiceTests : UmbracoIntegrationTest { var result = await DataTypeContainerService.DeleteAsync(Guid.NewGuid(), Constants.Security.SuperUserKey); Assert.IsFalse(result.Success); - Assert.AreEqual(DataTypeContainerOperationStatus.NotFound, result.Status); + Assert.AreEqual(EntityContainerOperationStatus.NotFound, result.Status); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs index 2bb642fb09..829fae5e2c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/DataTypeServiceTests.cs @@ -174,7 +174,7 @@ public class DataTypeServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Create_DataType_In_Container() { - var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }, null, Constants.Security.SuperUserKey)).Result; + var container = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; var result = await DataTypeService.CreateAsync( new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) @@ -205,7 +205,7 @@ public class DataTypeServiceTests : UmbracoIntegrationTest }, Constants.Security.SuperUserKey)).Result; - var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }, null, Constants.Security.SuperUserKey)).Result; + var container = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; dataType = await DataTypeService.GetAsync(dataType.Key); Assert.IsNotNull(dataType); @@ -223,7 +223,7 @@ public class DataTypeServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Move_DataType_To_Root() { - var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }, null, Constants.Security.SuperUserKey)).Result; + var container = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; var dataType = (await DataTypeService.CreateAsync( new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) { @@ -272,7 +272,7 @@ public class DataTypeServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Copy_DataType_To_Container() { - var container = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container" }, null, Constants.Security.SuperUserKey)).Result; + var container = (await DataTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; var dataType = (await DataTypeService.CreateAsync( new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) { @@ -296,8 +296,8 @@ public class DataTypeServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Copy_DataType_Between_Containers() { - var container1 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 1" }, null, Constants.Security.SuperUserKey)).Result; - var container2 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 2" }, null, Constants.Security.SuperUserKey)).Result; + var container1 = (await DataTypeContainerService.CreateAsync(null, "Root Container 1", null, Constants.Security.SuperUserKey)).Result; + var container2 = (await DataTypeContainerService.CreateAsync(null, "Root Container 2", null, Constants.Security.SuperUserKey)).Result; var dataType = (await DataTypeService.CreateAsync( new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) { @@ -326,7 +326,7 @@ public class DataTypeServiceTests : UmbracoIntegrationTest [Test] public async Task Can_Copy_DataType_From_Container_To_Root() { - var container1 = (await DataTypeContainerService.CreateAsync(new EntityContainer(Constants.ObjectTypes.DataType) { Name = "Root Container 1" }, null, Constants.Security.SuperUserKey)).Result; + var container1 = (await DataTypeContainerService.CreateAsync(null, "Root Container 1", null, Constants.Security.SuperUserKey)).Result; var dataType = (await DataTypeService.CreateAsync( new DataType(new LabelPropertyEditor(DataValueEditorFactory, IOHelper), ConfigurationEditorJsonSerializer) { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeContainerServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeContainerServiceTests.cs new file mode 100644 index 0000000000..90034887f5 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeContainerServiceTests.cs @@ -0,0 +1,204 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +/// +/// Tests covering the MediaTypeContainerService +/// +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class MediaTypeContainerServiceTests : UmbracoIntegrationTest +{ + private IMediaTypeContainerService MediaTypeContainerService => GetRequiredService(); + + private IMediaTypeService MediaTypeService => GetRequiredService(); + + [Test] + public async Task Can_Create_Container_At_Root() + { + var result = await MediaTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var created = await MediaTypeContainerService.GetAsync(result.Result.Key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Create_Child_Container() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; + + var result = await MediaTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var created = await MediaTypeContainerService.GetAsync(result.Result.Key); + Assert.NotNull(created); + Assert.AreEqual("Child Container", created.Name); + Assert.AreEqual(root.Id, created.ParentId); + } + + [Test] + public async Task Can_Create_Container_With_Explicit_Key() + { + var key = Guid.NewGuid(); + var result = await MediaTypeContainerService.CreateAsync(key, "Root Container", null, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + Assert.AreEqual(key, result.Result.Key); + + var created = await MediaTypeContainerService.GetAsync(key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Update_Container_At_Root() + { + var key = (await MediaTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result.Key; + + var result = await MediaTypeContainerService.UpdateAsync(key, "Root Container UPDATED", Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var updated = await MediaTypeContainerService.GetAsync(key); + Assert.NotNull(updated); + Assert.AreEqual("Root Container UPDATED", updated.Name); + Assert.AreEqual(Constants.System.Root, updated.ParentId); + } + + [Test] + public async Task Can_Update_Child_Container() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null, "Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await MediaTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await MediaTypeContainerService.UpdateAsync(child.Key, "Child Container UPDATED", Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + EntityContainer updated = await MediaTypeContainerService.GetAsync(child.Key); + Assert.NotNull(updated); + Assert.AreEqual("Child Container UPDATED", updated.Name); + Assert.AreEqual(root.Id, updated.ParentId); + } + + [Test] + public async Task Can_Get_Container_At_Root() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + EntityContainer created = await MediaTypeContainerService.GetAsync(root.Key); + Assert.NotNull(created); + Assert.AreEqual("Root Container", created.Name); + Assert.AreEqual(Constants.System.Root, created.ParentId); + } + + [Test] + public async Task Can_Get_Child_Container() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await MediaTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + EntityContainer created = await MediaTypeContainerService.GetAsync(child.Key); + Assert.IsNotNull(created); + Assert.AreEqual("Child Container", created.Name); + Assert.AreEqual(root.Id, child.ParentId); + } + + [Test] + public async Task Can_Delete_Container_At_Root() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + var result = await MediaTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + var current = await MediaTypeContainerService.GetAsync(root.Key); + Assert.IsNull(current); + } + + [Test] + public async Task Can_Delete_Child_Container() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await MediaTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await MediaTypeContainerService.DeleteAsync(child.Key, Constants.Security.SuperUserKey); + Assert.IsTrue(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.Success, result.Status); + + child = await MediaTypeContainerService.GetAsync(child.Key); + Assert.IsNull(child); + + root = await MediaTypeContainerService.GetAsync(root.Key); + Assert.IsNotNull(root); + } + + [Test] + public async Task Cannot_Create_Child_Container_Below_Invalid_Parent() + { + var key = Guid.NewGuid(); + var result = await MediaTypeContainerService.CreateAsync(key, "Child Container", Guid.NewGuid(), Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.ParentNotFound, result.Status); + + var created = await MediaTypeContainerService.GetAsync(key); + Assert.IsNull(created); + } + + [Test] + public async Task Cannot_Delete_Container_With_Child_Container() + { + EntityContainer root = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + EntityContainer child = (await MediaTypeContainerService.CreateAsync(null, "Child Container", root.Key, Constants.Security.SuperUserKey)).Result; + + var result = await MediaTypeContainerService.DeleteAsync(root.Key, Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); + + var current = await MediaTypeContainerService.GetAsync(root.Key); + Assert.IsNotNull(current); + } + + [Test] + public async Task Cannot_Delete_Container_With_Child_MediaType() + { + EntityContainer container = (await MediaTypeContainerService.CreateAsync(null,"Root Container", null, Constants.Security.SuperUserKey)).Result; + + IMediaType MediaType = new MediaType(ShortStringHelper, container.Id) + { + Alias = "test", Name = "Test" + }; + MediaTypeService.Save(MediaType); + + var result = await MediaTypeContainerService.DeleteAsync(container.Key, Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotEmpty, result.Status); + + var currentContainer = await MediaTypeContainerService.GetAsync(container.Key); + Assert.IsNotNull(currentContainer); + + var currentMediaType = MediaTypeService.Get(MediaType.Key); + Assert.IsNotNull(currentMediaType); + } + + [Test] + public async Task Cannot_Delete_Non_Existing_Container() + { + var result = await MediaTypeContainerService.DeleteAsync(Guid.NewGuid(), Constants.Security.SuperUserKey); + Assert.IsFalse(result.Success); + Assert.AreEqual(EntityContainerOperationStatus.NotFound, result.Status); + } +}