Add ancestor endpoints and remove explicit parent context (#15746)
* Remove explicit parent context in API outputs * Add ancestor endpoints for document and data type (experimental for now) * Add ancestor endpoints for doctypes, media, mediatypes, partial views, scripts, static files, stylesheets and templates * Add unit tests for ancestor ID parsing * Add ancestor endpoint for dictionary items * Update OpenApi.json * Fix merge and regenerate OpenApi.json * Regenerate OpenApi.json * Rename "folder" to "parent" for consistency * Fix merge * Fix merge * Include "self" in ancestor endpoints * Handle ancestors for root items correctly * Remove "type" from recycle bin items * Tests against fixed values instead of calculated ones. --------- Co-authored-by: Sven Geusens <sge@umbraco.dk>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsDataTypeTreeController : DataTypeTreeControllerBase
|
||||
{
|
||||
public AncestorsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
|
||||
: base(entityService, dataTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DataTypeTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsDictionaryTreeController : DictionaryTreeControllerBase
|
||||
{
|
||||
public AncestorsDictionaryTreeController(IEntityService entityService, IDictionaryItemService dictionaryItemService)
|
||||
: base(entityService, dictionaryItemService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<NamedEntityTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -23,6 +23,6 @@ public class ChildrenDictionaryTreeController : DictionaryTreeControllerBase
|
||||
{
|
||||
PagedModel<IDictionaryItem> paginatedItems = await DictionaryItemService.GetPagedAsync(parentId, skip, take);
|
||||
|
||||
return Ok(PagedViewModel(await MapTreeItemViewModels(parentId, paginatedItems.Items), paginatedItems.Total));
|
||||
return Ok(PagedViewModel(await MapTreeItemViewModels(paginatedItems.Items), paginatedItems.Total));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,38 @@ public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedE
|
||||
|
||||
protected IDictionaryItemService DictionaryItemService { get; }
|
||||
|
||||
protected async Task<IEnumerable<NamedEntityTreeItemResponseModel>> MapTreeItemViewModels(Guid? parentKey, IEnumerable<IDictionaryItem> dictionaryItems)
|
||||
protected async Task<IEnumerable<NamedEntityTreeItemResponseModel>> MapTreeItemViewModels(IEnumerable<IDictionaryItem> dictionaryItems)
|
||||
=> await Task.WhenAll(dictionaryItems.Select(CreateEntityTreeItemViewModelAsync));
|
||||
|
||||
protected override async Task<ActionResult<IEnumerable<NamedEntityTreeItemResponseModel>>> GetAncestors(Guid descendantKey, bool includeSelf = true)
|
||||
{
|
||||
async Task<NamedEntityTreeItemResponseModel> CreateEntityTreeItemViewModelAsync(IDictionaryItem dictionaryItem)
|
||||
IDictionaryItem? dictionaryItem = await DictionaryItemService.GetAsync(descendantKey);
|
||||
if (dictionaryItem is null)
|
||||
{
|
||||
// this looks weird - but we actually mimic how the rest of the ancestor (and children) endpoints actually work
|
||||
return Ok(Enumerable.Empty<NamedEntityTreeItemResponseModel>());
|
||||
}
|
||||
|
||||
var ancestors = new List<IDictionaryItem>();
|
||||
if (includeSelf)
|
||||
{
|
||||
ancestors.Add(dictionaryItem);
|
||||
}
|
||||
|
||||
while (dictionaryItem?.ParentId is not null)
|
||||
{
|
||||
dictionaryItem = await DictionaryItemService.GetAsync(dictionaryItem.ParentId.Value);
|
||||
if (dictionaryItem is not null)
|
||||
{
|
||||
ancestors.Add(dictionaryItem);
|
||||
}
|
||||
}
|
||||
|
||||
NamedEntityTreeItemResponseModel[] viewModels = await Task.WhenAll(ancestors.Select(CreateEntityTreeItemViewModelAsync));
|
||||
return Ok(viewModels.Reverse());
|
||||
}
|
||||
|
||||
private async Task<NamedEntityTreeItemResponseModel> CreateEntityTreeItemViewModelAsync(IDictionaryItem dictionaryItem)
|
||||
{
|
||||
var hasChildren = await DictionaryItemService.CountChildrenAsync(dictionaryItem.Key) > 0;
|
||||
return new NamedEntityTreeItemResponseModel
|
||||
@@ -37,15 +66,12 @@ public class DictionaryTreeControllerBase : NamedEntityTreeControllerBase<NamedE
|
||||
Name = dictionaryItem.ItemKey,
|
||||
Id = dictionaryItem.Key,
|
||||
HasChildren = hasChildren,
|
||||
Parent = parentKey.HasValue
|
||||
Parent = dictionaryItem.ParentId.HasValue
|
||||
? new ReferenceByIdModel
|
||||
{
|
||||
Id = parentKey.Value
|
||||
Id = dictionaryItem.ParentId.Value
|
||||
}
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
return await Task.WhenAll(dictionaryItems.Select(CreateEntityTreeItemViewModelAsync));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@ public class RootDictionaryTreeController : DictionaryTreeControllerBase
|
||||
{
|
||||
PagedModel<IDictionaryItem> paginatedItems = await DictionaryItemService.GetPagedAsync(null, skip, take);
|
||||
|
||||
return Ok(PagedViewModel(await MapTreeItemViewModels(null, paginatedItems.Items), paginatedItems.Total));
|
||||
return Ok(PagedViewModel(await MapTreeItemViewModels(paginatedItems.Items), paginatedItems.Total));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
public AncestorsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IDocumentPresentationFactory documentPresentationFactory)
|
||||
: base(
|
||||
entityService,
|
||||
userStartNodeEntitiesService,
|
||||
dataTypeService,
|
||||
publicAccessService,
|
||||
appCaches,
|
||||
backofficeSecurityAccessor,
|
||||
documentPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DocumentTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsDocumentTypeTreeController : DocumentTypeTreeControllerBase
|
||||
{
|
||||
public AncestorsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DocumentTypeTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Api.Management.Services.Entities;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsMediaTreeController : MediaTreeControllerBase
|
||||
{
|
||||
public AncestorsMediaTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
|
||||
IMediaPresentationFactory mediaPresentationFactory)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<MediaTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<MediaTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsMediaTypeTreeController : MediaTypeTreeControllerBase
|
||||
{
|
||||
public AncestorsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService)
|
||||
: base(entityService, mediaTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<MediaTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<MediaTypeTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsPartialViewTreeController : PartialViewTreeControllerBase
|
||||
{
|
||||
public AncestorsPartialViewTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemPresentationModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> Ancestors(string descendantPath)
|
||||
=> await GetAncestors(descendantPath);
|
||||
}
|
||||
@@ -15,14 +15,9 @@ public abstract class RecycleBinControllerBase<TItem> : ContentControllerBase
|
||||
where TItem : RecycleBinItemResponseModelBase, new()
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly string _itemUdiType;
|
||||
|
||||
protected RecycleBinControllerBase(IEntityService entityService)
|
||||
{
|
||||
_entityService = entityService;
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_itemUdiType = ItemObjectType.GetUdiType();
|
||||
}
|
||||
=> _entityService = entityService;
|
||||
|
||||
protected abstract UmbracoObjectTypes ItemObjectType { get; }
|
||||
|
||||
@@ -59,7 +54,6 @@ public abstract class RecycleBinControllerBase<TItem> : ContentControllerBase
|
||||
var viewModel = new TItem
|
||||
{
|
||||
Id = entity.Key,
|
||||
Type = _itemUdiType,
|
||||
HasChildren = entity.HasChildren,
|
||||
Parent = parentKey.HasValue
|
||||
? new ItemReferenceByIdResponseModel
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Script.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsScriptTreeController : ScriptTreeControllerBase
|
||||
{
|
||||
public AncestorsScriptTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemPresentationModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> Ancestors(string descendantPath)
|
||||
=> await GetAncestors(descendantPath);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.StaticFile.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsStaticFileTreeController : StaticFileTreeControllerBase
|
||||
{
|
||||
public AncestorsStaticFileTreeController(IPhysicalFileSystem physicalFileSystem)
|
||||
: base(physicalFileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemPresentationModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> Ancestors(string descendantPath)
|
||||
=> await GetAncestors(descendantPath);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.Tree;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
@@ -29,7 +30,12 @@ public class StaticFileTreeControllerBase : FileSystemTreeControllerBase
|
||||
? Array.Empty<string>()
|
||||
: base.GetFiles(path);
|
||||
|
||||
protected override FileSystemTreeItemPresentationModel[] GetAncestorModels(string path, bool includeSelf)
|
||||
=> IsAllowedPath(path)
|
||||
? base.GetAncestorModels(path, includeSelf)
|
||||
: Array.Empty<FileSystemTreeItemPresentationModel>();
|
||||
|
||||
private bool IsTreeRootPath(string path) => string.IsNullOrWhiteSpace(path);
|
||||
|
||||
private bool IsAllowedPath(string path) => _allowedRootFolders.Contains(path) || _allowedRootFolders.Any(folder => path.StartsWith($"{folder}/"));
|
||||
private bool IsAllowedPath(string path) => _allowedRootFolders.Contains(path) || _allowedRootFolders.Any(folder => path.StartsWith($"{folder}{Path.DirectorySeparatorChar}"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsStylesheetTreeController : StylesheetTreeControllerBase
|
||||
{
|
||||
public AncestorsStylesheetTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemPresentationModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> Ancestors(string descendantPath)
|
||||
=> await GetAncestors(descendantPath);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Tree;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AncestorsTemplateTreeController : TemplateTreeControllerBase
|
||||
{
|
||||
public AncestorsTemplateTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("ancestors")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<NamedEntityTreeItemResponseModel>>> Ancestors(Guid descendantId)
|
||||
=> await GetAncestors(descendantId);
|
||||
}
|
||||
@@ -3,10 +3,10 @@ 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;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Tree;
|
||||
|
||||
@@ -42,18 +42,41 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<IEnumerable<TItem>>> GetItems(Guid[] ids)
|
||||
protected virtual async Task<ActionResult<IEnumerable<TItem>>> GetAncestors(Guid descendantKey, bool includeSelf = true)
|
||||
{
|
||||
if (ids.IsCollectionEmpty())
|
||||
IEntitySlim[] ancestorEntities = await GetAncestorEntitiesAsync(descendantKey, includeSelf);
|
||||
|
||||
TItem[] result = ancestorEntities
|
||||
.Select(ancestor =>
|
||||
{
|
||||
return await Task.FromResult(Ok(PagedViewModel(Array.Empty<TItem>(), 0)));
|
||||
IEntitySlim? parent = ancestor.ParentId > 0
|
||||
? ancestorEntities.Single(a => a.Id == ancestor.ParentId)
|
||||
: null;
|
||||
|
||||
return MapTreeItemViewModel(parent?.Key, ancestor);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
IEntitySlim[] itemEntities = GetEntities(ids);
|
||||
protected virtual async Task<IEntitySlim[]> 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 await Task.FromResult(Array.Empty<IEntitySlim>());
|
||||
}
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(null, itemEntities);
|
||||
var ancestorIds = entity.AncestorIds();
|
||||
|
||||
return await Task.FromResult(Ok(treeItemViewModels));
|
||||
IEnumerable<IEntitySlim> ancestors = ancestorIds.Any()
|
||||
? EntityService.GetAll(ItemObjectType, ancestorIds)
|
||||
: Array.Empty<IEntitySlim>();
|
||||
ancestors = ancestors.Union(includeSelf ? new[] { entity } : Array.Empty<IEntitySlim>());
|
||||
|
||||
return ancestors.OrderBy(item => item.Level).ToArray();
|
||||
}
|
||||
|
||||
protected virtual IEntitySlim[] GetPagedRootEntities(int skip, int take, out long totalItems)
|
||||
@@ -77,8 +100,6 @@ public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerB
|
||||
ordering: ItemOrdering)
|
||||
.ToArray();
|
||||
|
||||
protected virtual IEntitySlim[] GetEntities(Guid[] ids) => EntityService.GetAll(ItemObjectType, ids).ToArray();
|
||||
|
||||
protected virtual TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
=> entities.Select(entity => MapTreeItemViewModel(parentKey, entity)).ToArray();
|
||||
|
||||
|
||||
@@ -28,6 +28,30 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected virtual async Task<ActionResult<IEnumerable<FileSystemTreeItemPresentationModel>>> GetAncestors(string path, bool includeSelf = true)
|
||||
{
|
||||
path = path.VirtualPathToSystemPath();
|
||||
FileSystemTreeItemPresentationModel[] models = GetAncestorModels(path, includeSelf);
|
||||
|
||||
return await Task.FromResult(Ok(models));
|
||||
}
|
||||
|
||||
protected virtual FileSystemTreeItemPresentationModel[] GetAncestorModels(string path, bool includeSelf)
|
||||
{
|
||||
var directories = path.Split(Path.DirectorySeparatorChar).Take(Range.EndAt(Index.FromEnd(1))).ToArray();
|
||||
var result = directories
|
||||
.Select((directory, index) => MapViewModel(string.Join(Path.DirectorySeparatorChar, directories.Take(index + 1)), directory, true))
|
||||
.ToList();
|
||||
|
||||
if (includeSelf)
|
||||
{
|
||||
var selfIsFolder = FileSystem.FileExists(path) is false;
|
||||
result.Add(MapViewModel(path, GetFileSystemItemName(selfIsFolder, path), selfIsFolder));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
protected virtual string[] GetDirectories(string path) => FileSystem
|
||||
.GetDirectories(path)
|
||||
.OrderBy(directory => directory)
|
||||
@@ -54,7 +78,7 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
|
||||
FileSystemTreeItemPresentationModel ViewModel(string itemPath, bool isFolder)
|
||||
=> MapViewModel(
|
||||
itemPath,
|
||||
isFolder ? Path.GetFileName(itemPath) : FileSystem.GetFileName(itemPath),
|
||||
GetFileSystemItemName(isFolder, itemPath),
|
||||
isFolder);
|
||||
|
||||
return allItems
|
||||
@@ -64,6 +88,10 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private string GetFileSystemItemName(bool isFolder, string itemPath) => isFolder
|
||||
? Path.GetFileName(itemPath)
|
||||
: FileSystem.GetFileName(itemPath);
|
||||
|
||||
private PagedViewModel<FileSystemTreeItemPresentationModel> PagedViewModel(IEnumerable<FileSystemTreeItemPresentationModel> viewModels, long totalItems)
|
||||
=> new() { Total = totalItems, Items = viewModels };
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Tree;
|
||||
|
||||
@@ -47,6 +49,29 @@ public abstract class FolderTreeControllerBase<TItem> : NamedEntityTreeControlle
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
protected override async Task<IEntitySlim[]> GetAncestorEntitiesAsync(Guid descendantKey, bool includeSelf = true)
|
||||
{
|
||||
IEntitySlim? entity = EntityService.Get(descendantKey, ItemObjectType)
|
||||
?? EntityService.Get(descendantKey, FolderObjectType);
|
||||
if (entity is null)
|
||||
{
|
||||
// not much else we can do here but return nothing
|
||||
return await Task.FromResult(Array.Empty<IEntitySlim>());
|
||||
}
|
||||
|
||||
var ancestorIds = entity.AncestorIds();
|
||||
// annoyingly we can't use EntityService.GetAll() with container object types, so we have to get them one by one
|
||||
IEntitySlim[] containers = ancestorIds.Select(id => EntityService.Get(id, FolderObjectType)).WhereNotNull().ToArray();
|
||||
IEnumerable<IEntitySlim> ancestors = ancestorIds.Any()
|
||||
? EntityService
|
||||
.GetAll(ItemObjectType, ancestorIds)
|
||||
.Union(containers)
|
||||
: Array.Empty<IEntitySlim>();
|
||||
ancestors = ancestors.Union(includeSelf ? new[] { entity } : Array.Empty<IEntitySlim>());
|
||||
|
||||
return ancestors.OrderBy(item => item.Level).ToArray();
|
||||
}
|
||||
|
||||
private IEntitySlim[] GetEntities(Guid? parentKey, int skip, int take, out long totalItems)
|
||||
{
|
||||
totalItems = 0;
|
||||
|
||||
@@ -48,14 +48,6 @@ public abstract class UserStartNodeTreeControllerBase<TItem> : EntityTreeControl
|
||||
: CalculateAccessMap(() => _userStartNodeEntitiesService.ChildUserAccessEntities(children, UserStartNodePaths), out totalItems);
|
||||
}
|
||||
|
||||
protected override IEntitySlim[] GetEntities(Guid[] keys)
|
||||
{
|
||||
IEntitySlim[] entities = base.GetEntities(keys);
|
||||
return UserHasRootAccess() || IgnoreUserStartNodes()
|
||||
? entities
|
||||
: CalculateAccessMap(() => _userStartNodeEntitiesService.UserAccessEntities(entities, UserStartNodePaths), out _);
|
||||
}
|
||||
|
||||
protected override TItem[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
if (UserHasRootAccess() || IgnoreUserStartNodes())
|
||||
|
||||
@@ -1818,6 +1818,75 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/data-type/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Data Type"
|
||||
],
|
||||
"operationId": "GetTreeDataTypeAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/data-type/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -2985,6 +3054,129 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/dictionary/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Dictionary"
|
||||
],
|
||||
"operationId": "GetTreeDictionaryAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/dictionary/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -4915,6 +5107,75 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/document-type/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document Type"
|
||||
],
|
||||
"operationId": "GetTreeDocumentTypeAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/document-type/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -8535,6 +8796,75 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/document/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document"
|
||||
],
|
||||
"operationId": "GetTreeDocumentAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/document/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -12748,6 +13078,75 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/media-type/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Media Type"
|
||||
],
|
||||
"operationId": "GetTreeMediaTypeAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/media-type/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -14932,6 +15331,75 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/media/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Media"
|
||||
],
|
||||
"operationId": "GetTreeMediaAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/media/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -19841,6 +20309,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/partial-view/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Partial View"
|
||||
],
|
||||
"operationId": "GetTreePartialViewAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantPath",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/partial-view/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -22378,6 +22914,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/script/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Script"
|
||||
],
|
||||
"operationId": "GetTreeScriptAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantPath",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/script/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -23387,6 +23991,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/static-file/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Static File"
|
||||
],
|
||||
"operationId": "GetTreeStaticFileAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantPath",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/static-file/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -24527,6 +25199,74 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/stylesheet/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stylesheet"
|
||||
],
|
||||
"operationId": "GetTreeStylesheetAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantPath",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FileSystemTreeItemPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/stylesheet/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -25664,6 +26404,129 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/template/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Template"
|
||||
],
|
||||
"operationId": "GetTreeTemplateAncestors",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "descendantId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/NamedEntityTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DataTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/FolderTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MediaTypeTreeItemResponseModel"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/RelationTypeTreeItemResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/tree/template/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -38786,8 +39649,7 @@
|
||||
"RecycleBinItemResponseModelBaseModel": {
|
||||
"required": [
|
||||
"hasChildren",
|
||||
"id",
|
||||
"type"
|
||||
"id"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -38795,10 +39657,6 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"type": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"hasChildren": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -8,9 +8,6 @@ public abstract class RecycleBinItemResponseModelBase
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
|
||||
13
src/Umbraco.Core/Extensions/TreeEntityExtensions.cs
Normal file
13
src/Umbraco.Core/Extensions/TreeEntityExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Core.Extensions;
|
||||
|
||||
public static class TreeEntityExtensions
|
||||
{
|
||||
public static int[] AncestorIds(this ITreeEntity entity) => entity.Path
|
||||
.Split(Constants.CharArrays.Comma)
|
||||
.Select(item => int.Parse(item, CultureInfo.InvariantCulture))
|
||||
.Take(new Range(Index.FromStart(1), Index.FromEnd(1)))
|
||||
.ToArray();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Range = System.Range;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Extensions;
|
||||
|
||||
[TestFixture]
|
||||
public class TreeEntityExtensionsTests
|
||||
{
|
||||
[TestCase("-1,1234", new int[] { })]
|
||||
[TestCase("-1,1234,5678", new int[] { 1234 })]
|
||||
[TestCase("-1,1234,5678,9012", new int[] { 5678, 1234 })]
|
||||
[TestCase("-1,1234,5678,9012,2345", new int[] { 9012, 5678, 1234 })]
|
||||
public void Parse_Ancestor_Ids_Excludes_Root_And_Self(string path, int[] expectedIds)
|
||||
{
|
||||
var entityMock = new Mock<ITreeEntity>();
|
||||
entityMock.SetupGet(m => m.Path).Returns(path);
|
||||
|
||||
var result = entityMock.Object.AncestorIds();
|
||||
Assert.AreEqual(expectedIds.Length, result.Length);
|
||||
Assert.That(expectedIds, Is.EquivalentTo(result));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user