diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs index 75d6f41ec9..0310748d00 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/ItemDocumentBlueprintController.cs @@ -29,6 +29,11 @@ public class ItemDocumentBlueprintController : DocumentBlueprintItemControllerBa CancellationToken cancellationToken, [FromQuery(Name = "id")] HashSet ids) { + if (ids.Count is 0) + { + return Ok(Enumerable.Empty()); + } + IEnumerable documents = _entityService .GetAll(UmbracoObjectTypes.DocumentBlueprint, ids.ToArray()) .Select(x => x as IDocumentEntitySlim) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs new file mode 100644 index 0000000000..9124b1ae2a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Tree/AncestorsDocumentBlueprintTreeController.cs @@ -0,0 +1,23 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree; + +[ApiVersion("1.0")] +public class AncestorsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase +{ + public AncestorsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory) + : base(entityService, documentPresentationFactory) + { + } + + [HttpGet("ancestors")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task>> Ancestors(CancellationToken cancellationToken, Guid descendantId) + => await GetAncestors(descendantId); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentBlueprintForDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentBlueprintForDocumentTypeController.cs new file mode 100644 index 0000000000..158e8062db --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentBlueprintForDocumentTypeController.cs @@ -0,0 +1,56 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; + +[ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] +public class DocumentBlueprintForDocumentTypeController : DocumentTypeControllerBase +{ + private readonly IContentBlueprintEditingService _contentBlueprintEditingService; + + private readonly IUmbracoMapper _umbracoMapper; + + public DocumentBlueprintForDocumentTypeController(IContentBlueprintEditingService contentBlueprintEditingService, IUmbracoMapper umbracoMapper) + { + _contentBlueprintEditingService = contentBlueprintEditingService; + _umbracoMapper = umbracoMapper; + } + + [HttpGet("{id:guid}/blueprint")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task DocumentBlueprintByDocumentTypeKey( + CancellationToken cancellationToken, + Guid id, + int skip = 0, + int take = 100) + { + Attempt?, ContentEditingOperationStatus> attempt = await _contentBlueprintEditingService.GetPagedByContentTypeAsync(id, skip, take); + if (attempt.Success is false) + { + return ContentEditingOperationStatusResult(attempt.Status); + } + + List viewModels = _umbracoMapper.MapEnumerable(attempt.Result!.Items); + + var pagedViewModel = new PagedViewModel + { + Total = attempt.Result!.Total, + Items = viewModels, + }; + + return Ok(pagedViewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs index 4eb3dd2426..763a1e2934 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs @@ -107,4 +107,15 @@ public abstract class DocumentTypeControllerBase : ManagementApiControllerBase .Build()), _ => new ObjectResult("Unknown content type structure operation status") { StatusCode = StatusCodes.Status500InternalServerError } }); + + protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status) => + OperationStatusResult(status, problemDetailsBuilder => status switch + { + ContentEditingOperationStatus.ContentTypeNotFound => NotFound(problemDetailsBuilder + .WithTitle("The specified document type was not found") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder + .WithTitle("Unknown content editing operation status") + .Build()), + }); } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs index d7137623df..2a5ea3ae7c 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs @@ -19,6 +19,7 @@ public class DocumentTypeMapDefinition : ContentTypeMapDefinition((_, _) => new DocumentTypeCollectionReferenceResponseModel(), Map); mapper.Define((_, _) => new DocumentTypeReferenceResponseModel(), Map); mapper.Define((_, _) => new DocumentTypeReferenceResponseModel(), Map); + mapper.Define((_, _) => new DocumentTypeBlueprintItemResponseModel(), Map); } // Umbraco.Code.MapAll @@ -114,4 +115,11 @@ public class DocumentTypeMapDefinition : ContentTypeMapDefinition?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take) + { + IContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey); + if (contentType is null) + { + return Attempt.FailWithStatus?, ContentEditingOperationStatus>(ContentEditingOperationStatus.ContentTypeNotFound, null); + } + + IContent[] blueprints = ContentService.GetBlueprintsForContentTypes([contentType.Id]).ToArray(); + + var result = new PagedModel + { + Items = blueprints.Skip(skip).Take(take), + Total = blueprints.Length, + }; + + return Attempt.SucceedWithStatus?, ContentEditingOperationStatus>(ContentEditingOperationStatus.Success, result); + } + public async Task> CreateAsync(ContentBlueprintCreateModel createModel, Guid userKey) { if (await ValidateCulturesAsync(createModel) is false) diff --git a/src/Umbraco.Core/Services/IContentBlueprintEditingService.cs b/src/Umbraco.Core/Services/IContentBlueprintEditingService.cs index 4c4fa6c066..66a940c16c 100644 --- a/src/Umbraco.Core/Services/IContentBlueprintEditingService.cs +++ b/src/Umbraco.Core/Services/IContentBlueprintEditingService.cs @@ -8,6 +8,11 @@ public interface IContentBlueprintEditingService { Task GetAsync(Guid key); + Task?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync( + Guid contentTypeKey, + int skip, + int take); + Task> CreateAsync(ContentBlueprintCreateModel createModel, Guid userKey); Task> CreateFromContentAsync(Guid contentKey, string name, Guid? key, Guid userKey); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.Get.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.Get.cs index 4852136d30..24207e1246 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.Get.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.Get.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; public partial class ContentBlueprintEditingServiceTests { [TestCase(true)] - [TestCase(true)] + [TestCase(false)] public async Task Can_Get(bool variant) { var blueprint = await (variant ? CreateVariantContentBlueprint() : CreateInvariantContentBlueprint()); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetPagedByContentTypeKey.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetPagedByContentTypeKey.cs new file mode 100644 index 0000000000..eb0190b83a --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentBlueprintEditingServiceTests.GetPagedByContentTypeKey.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + +public partial class ContentBlueprintEditingServiceTests +{ + [Test] + public async Task Can_Get_Paged() + { + var contentType = CreateInvariantContentType(); + + for (var i = 1; i < 6; i++) + { + var createModel = new ContentBlueprintCreateModel + { + ContentTypeKey = contentType.Key, + InvariantName = $"Blueprint {i}", + }; + + await ContentBlueprintEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + } + + var result = await ContentBlueprintEditingService.GetPagedByContentTypeAsync(contentType.Key, 0, 2); + + var pagedResult = result.Result; + Assert.Multiple(() => + { + Assert.IsTrue(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status); + Assert.IsNotNull(pagedResult); + }); + Assert.Multiple(() => + { + Assert.AreEqual(2, pagedResult.Items.Count()); + Assert.AreEqual(5, pagedResult.Total); + }); + } + + [Test] + public async Task Cannot_Get_Paged_With_Non_Existing_Content_Type() + { + var result = await ContentBlueprintEditingService.GetPagedByContentTypeAsync(Guid.NewGuid(), 0, 10); + + Assert.Multiple(() => + { + Assert.IsFalse(result.Success); + Assert.AreEqual(ContentEditingOperationStatus.ContentTypeNotFound, result.Status); + Assert.IsNull(result.Result); + }); + } +} diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs index f162e2d56b..a293f891b9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Get.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; public partial class ContentEditingServiceTests { [TestCase(true)] - [TestCase(true)] + [TestCase(false)] public async Task Can_Get(bool variant) { var content = await (variant ? CreateVariantContent() : CreateInvariantContent()); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index 98882ea615..2ffdc7c976 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -139,6 +139,12 @@ ContentBlueprintEditingServiceTests.cs + + ContentBlueprintEditingServiceTests.cs + + + ContentBlueprintEditingServiceTests.cs + ContentBlueprintEditingServiceTests.cs