using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Controllers.Tree; public abstract class EntityTreeControllerBase : ManagementApiControllerBase where TItem : EntityTreeItemResponseModel, new() { protected EntityTreeControllerBase(IEntityService entityService) => EntityService = entityService; protected IEntityService EntityService { get; } protected abstract UmbracoObjectTypes ItemObjectType { get; } protected virtual Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text)); protected Task>> GetRoot(int skip, int take) { IEntitySlim[] rootEntities = GetPagedRootEntities(skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities); PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); return Task.FromResult>>(Ok(result)); } protected Task>> GetChildren(Guid parentId, int skip, int take) { IEntitySlim[] children = GetPagedChildEntities(parentId, skip, take, out var totalItems); TItem[] treeItemViewModels = MapTreeItemViewModels(parentId, children); PagedViewModel result = PagedViewModel(treeItemViewModels, totalItems); return Task.FromResult>>(Ok(result)); } protected Task>> GetSiblings(Guid target, int before, int after) { IEntitySlim[] siblings = GetSiblingEntities(target, before, after); if (siblings.Length == 0) { return Task.FromResult>>(NotFound()); } IEntitySlim? entity = siblings.FirstOrDefault(); Guid? parentKey = entity?.ParentId > 0 ? EntityService.GetKey(entity.ParentId, ItemObjectType).Result : Constants.System.RootKey; TItem[] treeItemsViewModels = MapTreeItemViewModels(parentKey, siblings); return Task.FromResult>>(Ok(treeItemsViewModels)); } protected virtual async Task>> GetAncestors(Guid descendantKey, bool includeSelf = true) { IEntitySlim[] ancestorEntities = await GetAncestorEntitiesAsync(descendantKey, includeSelf); TItem[] result = ancestorEntities .Select(ancestor => { IEntitySlim? parent = ancestor.ParentId > 0 ? ancestorEntities.Single(a => a.Id == ancestor.ParentId) : null; return MapTreeItemViewModel(parent?.Key, ancestor); }) .ToArray(); return Ok(result); } protected virtual Task GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf) { IEntitySlim? entity = EntityService.Get(descendantKey, ItemObjectType); if (entity is null) { // not much else we can do here but return nothing return Task.FromResult(Array.Empty()); } var ancestorIds = entity.AncestorIds(); IEnumerable ancestors = ancestorIds.Any() ? EntityService.GetAll(ItemObjectType, ancestorIds) : Array.Empty(); ancestors = ancestors.Union(includeSelf ? new[] { entity } : Array.Empty()); return Task.FromResult(ancestors.OrderBy(item => item.Level).ToArray()); } protected virtual IEntitySlim[] GetPagedRootEntities(int skip, int take, out long totalItems) => EntityService .GetPagedChildren( Constants.System.RootKey, ItemObjectType, skip, take, out totalItems, ordering: ItemOrdering) .ToArray(); protected virtual IEntitySlim[] GetPagedChildEntities(Guid parentKey, int skip, int take, out long totalItems) => EntityService .GetPagedChildren( parentKey, ItemObjectType, skip, take, out totalItems, ordering: ItemOrdering) .ToArray(); protected virtual IEntitySlim[] GetSiblingEntities(Guid target, int before, int after) => EntityService .GetSiblings( target, ItemObjectType, before, after, ordering: ItemOrdering) .ToArray(); protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities) => entities.Select(entity => MapTreeItemViewModel(parentKey, entity)).ToArray(); protected virtual TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity) { var viewModel = new TItem { Id = entity.Key, HasChildren = entity.HasChildren, Parent = parentKey.HasValue ? new ReferenceByIdModel { Id = parentKey.Value } : null }; return viewModel; } protected PagedViewModel PagedViewModel(IEnumerable treeItemViewModels, long totalItems) => new() { Total = totalItems, Items = treeItemViewModels }; }