diff --git a/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs index 79ace9c8d5..7f36013e08 100644 --- a/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs @@ -43,4 +43,11 @@ public class ContentControllerBase : ManagementApiControllerBase ContentEditingOperationStatus.Unknown => StatusCode(StatusCodes.Status500InternalServerError, "Unknown error. Please see the log for more details."), _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status.") }; + + protected IActionResult ContentCreatingOperationStatusResult(ContentCreatingOperationStatus status) => + status switch + { + ContentCreatingOperationStatus.NotFound => NotFound("The content type could not be found"), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status."), + }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenByKeyDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenByKeyDocumentController.cs new file mode 100644 index 0000000000..3039c3c3aa --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenByKeyDocumentController.cs @@ -0,0 +1,48 @@ +using Asp.Versioning; +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; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +public class AllowedChildrenByKeyDocumentController : DocumentControllerBase +{ + private readonly IUmbracoMapper _umbracoMapper; + private readonly IContentCreatingService _contentCreatingService; + + public AllowedChildrenByKeyDocumentController(IUmbracoMapper umbracoMapper, IContentCreatingService contentCreatingService) + { + _umbracoMapper = umbracoMapper; + _contentCreatingService = contentCreatingService; + } + + [HttpGet("{id:guid}/allowed-document-types")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task AllowedChildrenByKey(Guid id, int skip = 0, int take = 100) + { + Attempt?, ContentCreatingOperationStatus> allowedChildrenAttempt = await _contentCreatingService.GetAllowedChildrenContentTypesAsync(id, skip, take); + + if (allowedChildrenAttempt.Success is false) + { + return ContentCreatingOperationStatusResult(allowedChildrenAttempt.Status); + } + + List viewModels = _umbracoMapper.MapEnumerable(allowedChildrenAttempt.Result!.Items); + + var pagedViewModel = new PagedViewModel + { + Total = allowedChildrenAttempt.Result.Total, + Items = viewModels, + }; + + return Ok(pagedViewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenOfRootDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenOfRootDocumentController.cs new file mode 100644 index 0000000000..4bfa6b6662 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/AllowedChildrenOfRootDocumentController.cs @@ -0,0 +1,41 @@ +using Asp.Versioning; +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.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +public class AllowedChildrenOfRootDocumentController : DocumentControllerBase +{ + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoMapper _umbracoMapper; + + public AllowedChildrenOfRootDocumentController(IContentTypeService contentTypeService, IUmbracoMapper umbracoMapper) + { + _contentTypeService = contentTypeService; + _umbracoMapper = umbracoMapper; + } + + [HttpGet("root/allowed-document-types")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task AllowedChildrenOfRoot(int skip = 0, int take = 100) + { + PagedModel allowedChildrenOfRoot = await _contentTypeService.GetAllAllowedAsRootAsync(skip, take); + + List viewModels = _umbracoMapper.MapEnumerable(allowedChildrenOfRoot.Items); + + var pagedViewModel = new PagedViewModel + { + Total = allowedChildrenOfRoot.Total, + Items = viewModels, + }; + + return await Task.FromResult(Ok(pagedViewModel)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs index 3fe5280f11..937457858b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs @@ -1,5 +1,4 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Content; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 1c44de2990..bb07971323 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -298,6 +298,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Services/ContentCreatingService.cs b/src/Umbraco.Core/Services/ContentCreatingService.cs new file mode 100644 index 0000000000..182fb33963 --- /dev/null +++ b/src/Umbraco.Core/Services/ContentCreatingService.cs @@ -0,0 +1,52 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public class ContentCreatingService : IContentCreatingService +{ + private readonly IContentTypeService _contentTypeService; + private readonly IContentService _contentService; + private readonly IEntityService _entityService; + private readonly ICoreScopeProvider _coreScopeProvider; + + public ContentCreatingService(IContentTypeService contentTypeService, IContentService contentService, IEntityService entityService, ICoreScopeProvider coreScopeProvider) + { + _contentTypeService = contentTypeService; + _contentService = contentService; + _entityService = entityService; + _coreScopeProvider = coreScopeProvider; + } + + public async Task?, ContentCreatingOperationStatus>> GetAllowedChildrenContentTypesAsync(Guid key, int skip, int take) + { + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + IContent? content = _contentService.GetById(key); + + if (content is null) + { + return Attempt.FailWithStatus?, ContentCreatingOperationStatus>(ContentCreatingOperationStatus.NotFound, null); + } + + // FIXME: When content gets a ContentTypeKey property, we no longer have to get the key from the entityService + Attempt contentTypeKeyAttempt = _entityService.GetKey(content.ContentTypeId, UmbracoObjectTypes.DocumentType); + + if (contentTypeKeyAttempt.Success is false) + { + // This should never happen, content shouldn't be able to exists without a document type + throw new InvalidOperationException("The document type could not be found."); + } + + Attempt?, ContentTypeOperationStatus> contentTypeAttempt = await _contentTypeService.GetAllowedChildrenAsync(contentTypeKeyAttempt.Result, skip, take); + + if (contentTypeAttempt.Success is false) + { + throw new InvalidOperationException("The document type could not be found."); + } + + scope.Complete(); + + return Attempt.SucceedWithStatus(ContentCreatingOperationStatus.Success, contentTypeAttempt.Result); + } +} diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 39adcf0daf..0e729da8e7 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -2,9 +2,11 @@ using Microsoft.Extensions.Logging; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence.Querying; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Changes; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -82,6 +84,47 @@ public class ContentTypeService : ContentTypeServiceBase + public Task> GetAllAllowedAsRootAsync(int skip, int take) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + + // that one is special because it works across content, media and member types + scope.ReadLock(Constants.Locks.ContentTypes, Constants.Locks.MediaTypes, Constants.Locks.MemberTypes); + + IQuery query = ScopeProvider.CreateQuery().Where(x => x.AllowedAsRoot); + IEnumerable contentTypes = Repository.Get(query).ToArray(); + + var pagedModel = new PagedModel + { + Total = contentTypes.Count(), + Items = contentTypes.Skip(skip).Take(take) + }; + + return Task.FromResult(pagedModel); + } + + public Task?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + IContentType? parent = Get(key); + + if (parent?.AllowedContentTypes is null) + { + return Task.FromResult(Attempt.FailWithStatus?, ContentTypeOperationStatus>(ContentTypeOperationStatus.NotFound, null)); + } + + IContentType[] allowedChildren = GetAll(parent.AllowedContentTypes.Select(x => x.Key)).ToArray(); + + var result = new PagedModel + { + Items = allowedChildren.Take(take).Skip(skip), + Total = allowedChildren.Length, + }; + + return Task.FromResult(Attempt.SucceedWithStatus?, ContentTypeOperationStatus>(ContentTypeOperationStatus.Success, result)); + } + protected override void DeleteItemsOfTypes(IEnumerable typeIds) { using (ICoreScope scope = ScopeProvider.CreateCoreScope()) diff --git a/src/Umbraco.Core/Services/IContentCreatingService.cs b/src/Umbraco.Core/Services/IContentCreatingService.cs new file mode 100644 index 0000000000..6f4e292bec --- /dev/null +++ b/src/Umbraco.Core/Services/IContentCreatingService.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface IContentCreatingService +{ + Task?, ContentCreatingOperationStatus>> GetAllowedChildrenContentTypesAsync(Guid key, int skip, int take); +} diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index d38139349b..b534c836d3 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -1,4 +1,5 @@ using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; @@ -29,4 +30,16 @@ public interface IContentTypeService : IContentTypeBaseService /// /// IEnumerable GetAllContentTypeIds(string[] aliases); + + /// + /// Returns all the content type allowed as root. + /// + /// + Task> GetAllAllowedAsRootAsync(int skip, int take); + + /// + /// Returns all content types allowed as children for a given content type key. + /// + /// + Task?, ContentTypeOperationStatus>> GetAllowedChildrenAsync(Guid key, int skip, int take); } diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentCreatingOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentCreatingOperationStatus.cs new file mode 100644 index 0000000000..a1eb3226a2 --- /dev/null +++ b/src/Umbraco.Core/Services/OperationStatus/ContentCreatingOperationStatus.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Core.Services.OperationStatus; + +public enum ContentCreatingOperationStatus +{ + Success, + NotFound +} diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs index e2b04cec5e..642b5530b4 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs @@ -6,5 +6,6 @@ public enum ContentTypeOperationStatus DuplicateAlias, InvalidAlias, InvalidPropertyTypeAlias, - InvalidDataType + InvalidDataType, + NotFound }