New backoffice - trees design (#12963)
* Refactor: Add default versioned back office route attribute * Tree controller bases and first draft implementations for document, media and doctype * Move tree item view models to appropriate location * Fix missing parent * Refactor user entity access for testability * A bit of clean-up + handle user start nodes for items endpoint * Implement foldersOnly for folder tree * Items endpoint for document type tree * Strongly typed action results * Content + media recycle bin * Correct return type for swagger * Member type tree * Rename user start node handling to make a little more sense * Revert to faked admin start nodes in document tree * Media type tree * Data type tree * Relation type tree * Remove unused dependency from member type tree * Correct documentation for member type tree endpoint response types * Use icon constants * Add templates tree * Member group tree * Document blueprint tree * Partial views, scripts and stylesheets trees * Static files tree * Clarify "folders only" state * Comments and improved readability * Rename TreeControllerBase and TreeItemViewModel * Move recycle bin controller base to its own namespace * Moved tree base controllers to their own namespace * Common base class for tree view models * Remove ProblemDetails response type declaration from all actions * Add OpenApiTag * Various review comments * Dictionary item tree * Renamed all tree controllers to follow action/feature naming convention * Handle client culture state for document tree * Support "ignore user start nodes" for content and media + refactor how tree states work to make things more explicit * Fix or postpone a few TODOs * Make entity service able to paginate trashed children * Handle sorting explicitly * Re-apply VersionedApiBackOfficeRoute to install and upgrade controllers after merge * Use PagedViewModel instead of PagedResult for all trees * Explain the usage of UmbracoObjectTypes.Unknown * Introduce and apply GetMany pattern for dictionary items * Add a note about relation type caching * Fix broken test build + add unit tests for new localization service methods * Use new management API controller base * Entity repository should build document entities for document blueprints when getting paged entities (same as it does when getting specific entities) * Use Media type for Media recycle bin Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Move shared relation service to concrete implementations * Use inclusive language * Add 401 response type documentation to applicable trees * Refactor entity load for folder tree controller base + ensure that folders are only included in the first result page * Add (in-memory) pagination to dictionary tree * Make file system controller honor paging parameters * Support pagination in relation type tree * Clarify method name a bit for detecting tree root path requests * Update Open API schema to match new trees * Move from page number and page size to skip/take (with temporary workaround for lack of property skip/take pagination in current DB implementation) * Update OpenAPI schema to match skip/take * Update OpenAPI schema * Don't return paginated view models from "items" endpoints * Update OpenApi schema Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DataType.Tree;
|
||||
|
||||
public class ChildrenDataTypeTreeController : DataTypeTreeControllerBase
|
||||
{
|
||||
public ChildrenDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
|
||||
: base(entityService, dataTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FolderTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DataType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DataType}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.DataType))]
|
||||
public class DataTypeTreeControllerBase : FolderTreeControllerBase<FolderTreeItemViewModel>
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
public DataTypeTreeControllerBase(IEntityService entityService, IDataTypeService dataTypeService)
|
||||
: base(entityService) =>
|
||||
_dataTypeService = dataTypeService;
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DataType;
|
||||
|
||||
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.DataTypeContainer;
|
||||
|
||||
protected override FolderTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var dataTypes = _dataTypeService
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
{
|
||||
FolderTreeItemViewModel viewModel = MapTreeItemViewModel(parentKey, entity);
|
||||
if (dataTypes.TryGetValue(entity.Id, out IDataType? dataType))
|
||||
{
|
||||
viewModel.Icon = dataType.Editor?.Icon ?? viewModel.Icon;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DataType.Tree;
|
||||
|
||||
public class ItemsDataTypeTreeController : DataTypeTreeControllerBase
|
||||
{
|
||||
public ItemsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
|
||||
: base(entityService, dataTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FolderTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DataType.Tree;
|
||||
|
||||
public class RootDataTypeTreeController : DataTypeTreeControllerBase
|
||||
{
|
||||
public RootDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
|
||||
: base(entityService, dataTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FolderTreeItemViewModel>>> Root(int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DictionaryItem.Tree;
|
||||
|
||||
public class ChildrenDictionaryItemTreeController : DictionaryItemTreeControllerBase
|
||||
{
|
||||
public ChildrenDictionaryItemTreeController(IEntityService entityService, ILocalizationService localizationService)
|
||||
: base(entityService, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IDictionaryItem[] dictionaryItems = PaginatedDictionaryItems(
|
||||
pageNumber,
|
||||
pageSize,
|
||||
LocalizationService.GetDictionaryItemChildren(parentKey),
|
||||
out var totalItems);
|
||||
|
||||
EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems);
|
||||
|
||||
PagedViewModel<EntityTreeItemViewModel> result = PagedViewModel(viewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DictionaryItem.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DictionaryItem}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.DictionaryItem))]
|
||||
// NOTE: at the moment dictionary items aren't supported by EntityService, so we have little use of the
|
||||
// tree controller base. We'll keep it though, in the hope that we can mend EntityService.
|
||||
public class DictionaryItemTreeControllerBase : EntityTreeControllerBase<EntityTreeItemViewModel>
|
||||
{
|
||||
public DictionaryItemTreeControllerBase(IEntityService entityService, ILocalizationService localizationService)
|
||||
: base(entityService) =>
|
||||
LocalizationService = localizationService;
|
||||
|
||||
// dictionary items do not currently have a known UmbracoObjectType, so we'll settle with Unknown for now
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Unknown;
|
||||
|
||||
protected ILocalizationService LocalizationService { get; }
|
||||
|
||||
protected EntityTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IDictionaryItem[] dictionaryItems)
|
||||
=> dictionaryItems.Select(dictionaryItem => new EntityTreeItemViewModel
|
||||
{
|
||||
Icon = Constants.Icons.RelationType,
|
||||
Name = dictionaryItem.ItemKey,
|
||||
Key = dictionaryItem.Key,
|
||||
Type = Constants.UdiEntityType.DictionaryItem,
|
||||
HasChildren = false,
|
||||
IsContainer = LocalizationService.GetDictionaryItemChildren(dictionaryItem.Key).Any(),
|
||||
ParentKey = parentKey
|
||||
}).ToArray();
|
||||
|
||||
// localization service does not (yet) allow pagination of dictionary items, we have to do it in memory for now
|
||||
protected IDictionaryItem[] PaginatedDictionaryItems(long pageNumber, int pageSize, IEnumerable<IDictionaryItem> allDictionaryItems, out long totalItems)
|
||||
{
|
||||
IDictionaryItem[] allDictionaryItemsAsArray = allDictionaryItems.ToArray();
|
||||
|
||||
totalItems = allDictionaryItemsAsArray.Length;
|
||||
return allDictionaryItemsAsArray
|
||||
.OrderBy(item => item.ItemKey)
|
||||
.Skip((int)pageNumber * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DictionaryItem.Tree;
|
||||
|
||||
public class ItemsDictionaryItemTreeController : DictionaryItemTreeControllerBase
|
||||
{
|
||||
public ItemsDictionaryItemTreeController(IEntityService entityService, ILocalizationService localizationService)
|
||||
: base(entityService, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FolderTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
{
|
||||
IDictionaryItem[] dictionaryItems = LocalizationService.GetDictionaryItemsByIds(keys).ToArray();
|
||||
|
||||
EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems);
|
||||
|
||||
return await Task.FromResult(Ok(viewModels));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DictionaryItem.Tree;
|
||||
|
||||
public class RootDictionaryItemTreeController : DictionaryItemTreeControllerBase
|
||||
{
|
||||
public RootDictionaryItemTreeController(IEntityService entityService, ILocalizationService localizationService)
|
||||
: base(entityService, localizationService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IDictionaryItem[] dictionaryItems = PaginatedDictionaryItems(
|
||||
pageNumber,
|
||||
pageSize,
|
||||
LocalizationService.GetRootDictionaryItems(),
|
||||
out var totalItems);
|
||||
|
||||
EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, dictionaryItems);
|
||||
|
||||
PagedViewModel<EntityTreeItemViewModel> result = PagedViewModel(viewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.RecycleBin;
|
||||
|
||||
public class ChildrenDocumentRecycleBinController : DocumentRecycleBinControllerBase
|
||||
{
|
||||
public ChildrenDocumentRecycleBinController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<RecycleBinItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<RecycleBinItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100)
|
||||
=> await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.RecycleBin;
|
||||
using Umbraco.Cms.ManagementApi.Filters;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.RecycleBin;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Document}/recycle-bin")]
|
||||
[RequireDocumentTreeRootAccess]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Document))]
|
||||
public class DocumentRecycleBinControllerBase : RecycleBinControllerBase<RecycleBinItemViewModel>
|
||||
{
|
||||
public DocumentRecycleBinControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Document;
|
||||
|
||||
protected override int RecycleBinRootId => Constants.System.RecycleBinContent;
|
||||
|
||||
protected override RecycleBinItemViewModel MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
RecycleBinItemViewModel viewModel = base.MapRecycleBinViewModel(parentKey, entity);
|
||||
|
||||
if (entity is IDocumentEntitySlim documentEntitySlim)
|
||||
{
|
||||
viewModel.Icon = documentEntitySlim.ContentTypeIcon ?? viewModel.Icon;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.RecycleBin;
|
||||
|
||||
public class RootDocumentRecycleBinController : DocumentRecycleBinControllerBase
|
||||
{
|
||||
public RootDocumentRecycleBinController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<RecycleBinItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<RecycleBinItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.Tree;
|
||||
|
||||
public class ChildrenDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
public ChildrenDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, publicAccessService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<DocumentTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100, Guid? dataTypeKey = null, string? culture = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
RenderForClientCulture(culture);
|
||||
return await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Document}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Document))]
|
||||
public abstract class DocumentTreeControllerBase : UserStartNodeTreeControllerBase<DocumentTreeItemViewModel>
|
||||
{
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
private string? _culture;
|
||||
|
||||
protected DocumentTreeControllerBase(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService)
|
||||
{
|
||||
_publicAccessService = publicAccessService;
|
||||
_appCaches = appCaches;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Document;
|
||||
|
||||
protected override Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.SortOrder));
|
||||
|
||||
protected void RenderForClientCulture(string? culture) => _culture = culture;
|
||||
|
||||
protected override DocumentTreeItemViewModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
DocumentTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
|
||||
if (entity is IDocumentEntitySlim documentEntitySlim)
|
||||
{
|
||||
viewModel.IsPublished = documentEntitySlim.Published;
|
||||
viewModel.IsEdited = documentEntitySlim.Edited;
|
||||
viewModel.Icon = documentEntitySlim.ContentTypeIcon ?? viewModel.Icon;
|
||||
viewModel.IsProtected = _publicAccessService.IsProtected(entity.Path);
|
||||
|
||||
if (_culture != null && documentEntitySlim.Variations.VariesByCulture())
|
||||
{
|
||||
viewModel.Name = documentEntitySlim.CultureNames.TryGetValue(_culture, out var cultureName)
|
||||
? cultureName
|
||||
: $"({viewModel.Name})";
|
||||
|
||||
viewModel.IsPublished = documentEntitySlim.PublishedCultures.Contains(_culture);
|
||||
viewModel.IsEdited = documentEntitySlim.EditedCultures.Contains(_culture);
|
||||
}
|
||||
|
||||
viewModel.IsEdited &= viewModel.IsPublished;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
// TODO: delete these (faking start node setup for unlimited editor)
|
||||
protected override int[] GetUserStartNodeIds() => new[] { -1 };
|
||||
|
||||
protected override string[] GetUserStartNodePaths() => Array.Empty<string>();
|
||||
|
||||
// TODO: use these implementations instead of the dummy ones above once we have backoffice auth in place
|
||||
// protected override int[] GetUserStartNodeIds()
|
||||
// => _backofficeSecurityAccessor
|
||||
// .BackOfficeSecurity?
|
||||
// .CurrentUser?
|
||||
// .CalculateContentStartNodeIds(EntityService, _appCaches)
|
||||
// ?? Array.Empty<int>();
|
||||
//
|
||||
// protected override string[] GetUserStartNodePaths()
|
||||
// => _backofficeSecurityAccessor
|
||||
// .BackOfficeSecurity?
|
||||
// .CurrentUser?
|
||||
// .GetContentStartNodePaths(EntityService, _appCaches)
|
||||
// ?? Array.Empty<string>();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.Tree;
|
||||
|
||||
public class ItemsDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
public ItemsDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, publicAccessService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DocumentTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys, Guid? dataTypeKey = null, string? culture = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
RenderForClientCulture(culture);
|
||||
return await GetItems(keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Document.Tree;
|
||||
|
||||
public class RootDocumentTreeController : DocumentTreeControllerBase
|
||||
{
|
||||
public RootDocumentTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
IPublicAccessService publicAccessService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, publicAccessService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<DocumentTreeItemViewModel>>> Root(int skip = 0, int take = 100, Guid? dataTypeKey = null, string? culture = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
RenderForClientCulture(culture);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentBlueprint.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DocumentBlueprint}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.DocumentBlueprint))]
|
||||
public class DocumentBlueprintTreeControllerBase : EntityTreeControllerBase<DocumentBlueprintTreeItemViewModel>
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public DocumentBlueprintTreeControllerBase(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService) =>
|
||||
_contentTypeService = contentTypeService;
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DocumentBlueprint;
|
||||
|
||||
protected override DocumentBlueprintTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var contentTypeAliases = entities
|
||||
.OfType<IDocumentEntitySlim>()
|
||||
.Select(entity => entity.ContentTypeAlias)
|
||||
.ToArray();
|
||||
|
||||
var contentTypeIds = _contentTypeService.GetAllContentTypeIds(contentTypeAliases).ToArray();
|
||||
var contentTypeByAlias = _contentTypeService
|
||||
.GetAll(contentTypeIds)
|
||||
.ToDictionary(contentType => contentType.Alias);
|
||||
|
||||
return entities.Select(entity =>
|
||||
{
|
||||
DocumentBlueprintTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
viewModel.Icon = Constants.Icons.Blueprint;
|
||||
viewModel.HasChildren = false;
|
||||
|
||||
if (entity is IDocumentEntitySlim documentEntitySlim
|
||||
&& contentTypeByAlias.TryGetValue(documentEntitySlim.ContentTypeAlias, out IContentType? contentType))
|
||||
{
|
||||
viewModel.DocumentTypeKey = contentType.Key;
|
||||
viewModel.DocumentTypeAlias = contentType.Alias;
|
||||
viewModel.DocumentTypeName = contentType.Name;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentBlueprint.Tree;
|
||||
|
||||
public class ItemsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
|
||||
{
|
||||
public ItemsDocumentBlueprintTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentBlueprintTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DocumentBlueprintTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentBlueprint.Tree;
|
||||
|
||||
public class RootDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
|
||||
{
|
||||
public RootDocumentBlueprintTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentBlueprintTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<DocumentBlueprintTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentType.Tree;
|
||||
|
||||
public class ChildrenDocumentTypeTreeController : DocumentTypeTreeControllerBase
|
||||
{
|
||||
public ChildrenDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTypeTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<DocumentTypeTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.DocumentType}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.DocumentType))]
|
||||
public class DocumentTypeTreeControllerBase : FolderTreeControllerBase<DocumentTypeTreeItemViewModel>
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public DocumentTypeTreeControllerBase(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService) =>
|
||||
_contentTypeService = contentTypeService;
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DocumentType;
|
||||
|
||||
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.DocumentTypeContainer;
|
||||
|
||||
protected override DocumentTypeTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var contentTypes = _contentTypeService
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
{
|
||||
DocumentTypeTreeItemViewModel viewModel = MapTreeItemViewModel(parentKey, entity);
|
||||
if (contentTypes.TryGetValue(entity.Id, out IContentType? contentType))
|
||||
{
|
||||
viewModel.Icon = contentType.Icon ?? viewModel.Icon;
|
||||
viewModel.IsElement = contentType.IsElement;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentType.Tree;
|
||||
|
||||
public class ItemsDocumentTypeTreeController : DocumentTypeTreeControllerBase
|
||||
{
|
||||
public ItemsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<DocumentTypeTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<DocumentTypeTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.DocumentType.Tree;
|
||||
|
||||
public class RootDocumentTypeTreeController : DocumentTypeTreeControllerBase
|
||||
{
|
||||
public RootDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
|
||||
: base(entityService, contentTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<DocumentTypeTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<DocumentTypeTreeItemViewModel>>> Root(int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Umbraco.New.Cms.Web.Common.Routing;
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Install;
|
||||
|
||||
[ApiController]
|
||||
[BackOfficeRoute("api/v{version:apiVersion}/install")]
|
||||
[VersionedApiBackOfficeRoute("install")]
|
||||
[OpenApiTag("Install")]
|
||||
[RequireRuntimeLevel(RuntimeLevel.Install)]
|
||||
public abstract class InstallControllerBase : Controller
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.RecycleBin;
|
||||
|
||||
public class ChildrenMediaRecycleBinController : MediaRecycleBinControllerBase
|
||||
{
|
||||
public ChildrenMediaRecycleBinController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<RecycleBinItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<RecycleBinItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100)
|
||||
=> await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.RecycleBin;
|
||||
using Umbraco.Cms.ManagementApi.Filters;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.RecycleBin;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Media}/recycle-bin")]
|
||||
[RequireMediaTreeRootAccess]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Media))]
|
||||
public class MediaRecycleBinControllerBase : RecycleBinControllerBase<RecycleBinItemViewModel>
|
||||
{
|
||||
public MediaRecycleBinControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Media;
|
||||
|
||||
protected override int RecycleBinRootId => Constants.System.RecycleBinMedia;
|
||||
|
||||
protected override RecycleBinItemViewModel MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
RecycleBinItemViewModel viewModel = base.MapRecycleBinViewModel(parentKey, entity);
|
||||
|
||||
if (entity is IMediaEntitySlim mediaEntitySlim)
|
||||
{
|
||||
viewModel.Icon = mediaEntitySlim.ContentTypeIcon ?? viewModel.Icon;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.RecycleBin;
|
||||
|
||||
public class RootMediaRecycleBinController : MediaRecycleBinControllerBase
|
||||
{
|
||||
public RootMediaRecycleBinController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<RecycleBinItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<RecycleBinItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.Tree;
|
||||
|
||||
public class ChildrenMediaTreeController : MediaTreeControllerBase
|
||||
{
|
||||
public ChildrenMediaTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ContentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ContentTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100, Guid? dataTypeKey = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
return await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.Tree;
|
||||
|
||||
public class ItemsMediaTreeController : MediaTreeControllerBase
|
||||
{
|
||||
public ItemsMediaTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ContentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<ContentTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys, Guid? dataTypeKey = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
return await GetItems(keys);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Media}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Media))]
|
||||
public class MediaTreeControllerBase : UserStartNodeTreeControllerBase<ContentTreeItemViewModel>
|
||||
{
|
||||
private readonly AppCaches _appCaches;
|
||||
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
|
||||
|
||||
public MediaTreeControllerBase(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService)
|
||||
{
|
||||
_appCaches = appCaches;
|
||||
_backofficeSecurityAccessor = backofficeSecurityAccessor;
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Media;
|
||||
|
||||
protected override Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.SortOrder));
|
||||
|
||||
protected override ContentTreeItemViewModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
ContentTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
|
||||
if (entity is IMediaEntitySlim mediaEntitySlim)
|
||||
{
|
||||
viewModel.Icon = mediaEntitySlim.ContentTypeIcon ?? viewModel.Icon;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
// TODO: delete these (faking start node setup for unlimited editor)
|
||||
protected override int[] GetUserStartNodeIds() => new[] { -1 };
|
||||
|
||||
protected override string[] GetUserStartNodePaths() => Array.Empty<string>();
|
||||
|
||||
// TODO: use these implementations instead of the dummy ones above once we have backoffice auth in place
|
||||
// protected override int[] GetUserStartNodeIds()
|
||||
// => _backofficeSecurityAccessor
|
||||
// .BackOfficeSecurity?
|
||||
// .CurrentUser?
|
||||
// .CalculateMediaStartNodeIds(EntityService, _appCaches)
|
||||
// ?? Array.Empty<int>();
|
||||
//
|
||||
// protected override string[] GetUserStartNodePaths()
|
||||
// => _backofficeSecurityAccessor
|
||||
// .BackOfficeSecurity?
|
||||
// .CurrentUser?
|
||||
// .GetMediaStartNodePaths(EntityService, _appCaches)
|
||||
// ?? Array.Empty<string>();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Media.Tree;
|
||||
|
||||
public class RootMediaTreeController : MediaTreeControllerBase
|
||||
{
|
||||
public RootMediaTreeController(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService,
|
||||
AppCaches appCaches,
|
||||
IBackOfficeSecurityAccessor backofficeSecurityAccessor)
|
||||
: base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<ContentTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<ContentTreeItemViewModel>>> Root(int skip = 0, int take = 100, Guid? dataTypeKey = null)
|
||||
{
|
||||
IgnoreUserStartNodesForDataType(dataTypeKey);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MediaType.Tree;
|
||||
|
||||
public class ChildrenMediaTypeTreeController : MediaTypeTreeControllerBase
|
||||
{
|
||||
public ChildrenMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService)
|
||||
: base(entityService, mediaTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FolderTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MediaType.Tree;
|
||||
|
||||
public class ItemsMediaTypeTreeController : MediaTypeTreeControllerBase
|
||||
{
|
||||
public ItemsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService)
|
||||
: base(entityService, mediaTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FolderTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MediaType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MediaType}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.MediaType))]
|
||||
public class MediaTypeTreeControllerBase : FolderTreeControllerBase<FolderTreeItemViewModel>
|
||||
{
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
|
||||
public MediaTypeTreeControllerBase(IEntityService entityService, IMediaTypeService mediaTypeService)
|
||||
: base(entityService) =>
|
||||
_mediaTypeService = mediaTypeService;
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MediaType;
|
||||
|
||||
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MediaTypeContainer;
|
||||
|
||||
protected override FolderTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
{
|
||||
var mediaTypes = _mediaTypeService
|
||||
.GetAll(entities.Select(entity => entity.Id).ToArray())
|
||||
.ToDictionary(contentType => contentType.Id);
|
||||
|
||||
return entities.Select(entity =>
|
||||
{
|
||||
FolderTreeItemViewModel viewModel = MapTreeItemViewModel(parentKey, entity);
|
||||
if (mediaTypes.TryGetValue(entity.Id, out IMediaType? mediaType))
|
||||
{
|
||||
viewModel.Icon = mediaType.Icon ?? viewModel.Icon;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MediaType.Tree;
|
||||
|
||||
public class RootMediaTypeTreeController : MediaTypeTreeControllerBase
|
||||
{
|
||||
public RootMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService)
|
||||
: base(entityService, mediaTypeService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FolderTreeItemViewModel>>> Root(int skip = 0, int take = 100, bool foldersOnly = false)
|
||||
{
|
||||
RenderFoldersOnly(foldersOnly);
|
||||
return await GetRoot(skip, take);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberGroup.Tree;
|
||||
|
||||
public class ItemsMemberGroupTreeController : MemberGroupTreeControllerBase
|
||||
{
|
||||
public ItemsMemberGroupTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<EntityTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberGroup.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MemberGroup}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.MemberGroup))]
|
||||
public class MemberGroupTreeControllerBase : EntityTreeControllerBase<EntityTreeItemViewModel>
|
||||
{
|
||||
public MemberGroupTreeControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberGroup;
|
||||
|
||||
protected override EntityTreeItemViewModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
EntityTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
viewModel.Icon = Constants.Icons.MemberGroup;
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberGroup.Tree;
|
||||
|
||||
public class RootMemberGroupTreeController : MemberGroupTreeControllerBase
|
||||
{
|
||||
public RootMemberGroupTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberType.Tree;
|
||||
|
||||
public class ItemsMemberTypeTreeController : MemberTypeTreeControllerBase
|
||||
{
|
||||
public ItemsMemberTypeTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<EntityTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MemberType}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.MemberType))]
|
||||
public class MemberTypeTreeControllerBase : EntityTreeControllerBase<EntityTreeItemViewModel>
|
||||
{
|
||||
public MemberTypeTreeControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType;
|
||||
|
||||
protected override EntityTreeItemViewModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
EntityTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
viewModel.Icon = Constants.Icons.User;
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.MemberType.Tree;
|
||||
|
||||
public class RootMemberTypeTreeController : MemberTypeTreeControllerBase
|
||||
{
|
||||
public RootMemberTypeTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.PartialView.Tree;
|
||||
|
||||
public class ChildrenPartialViewTreeController : PartialViewTreeControllerBase
|
||||
{
|
||||
public ChildrenPartialViewTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Children(string path, int skip = 0, int take = 100)
|
||||
=> await GetChildren(path, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.PartialView.Tree;
|
||||
|
||||
public class ItemsPartialViewTreeController : PartialViewTreeControllerBase
|
||||
{
|
||||
public ItemsPartialViewTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemViewModel>>> Items([FromQuery(Name = "path")] string[] paths)
|
||||
=> await GetItems(paths);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.PartialView.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.PartialView}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.PartialView))]
|
||||
public class PartialViewTreeControllerBase : FileSystemTreeControllerBase
|
||||
{
|
||||
public PartialViewTreeControllerBase(FileSystems fileSystems)
|
||||
=> FileSystem = fileSystems.PartialViewsFileSystem ??
|
||||
throw new ArgumentException("Missing partial views file system", nameof(fileSystems));
|
||||
|
||||
protected override IFileSystem FileSystem { get; }
|
||||
|
||||
protected override string FileIcon(string path) => Constants.Icons.PartialView;
|
||||
|
||||
protected override string ItemType(string path) => Constants.UdiEntityType.PartialView;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.PartialView.Tree;
|
||||
|
||||
public class RootPartialViewTreeController : PartialViewTreeControllerBase
|
||||
{
|
||||
public RootPartialViewTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.RecycleBin;
|
||||
|
||||
public abstract class RecycleBinControllerBase<TItem> : Controller
|
||||
where TItem : RecycleBinItemViewModel, new()
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly string _itemUdiType;
|
||||
|
||||
protected RecycleBinControllerBase(IEntityService entityService)
|
||||
{
|
||||
_entityService = entityService;
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_itemUdiType = ItemObjectType.GetUdiType();
|
||||
}
|
||||
|
||||
protected abstract UmbracoObjectTypes ItemObjectType { get; }
|
||||
|
||||
protected abstract int RecycleBinRootId { get; }
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IEntitySlim[] rootEntities = GetPagedRootEntities(pageNumber, pageSize, out var totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapRecycleBinViewModels(null, rootEntities);
|
||||
|
||||
PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IEntitySlim[] children = GetPagedChildEntities(parentKey, pageNumber, pageSize, out var totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapRecycleBinViewModels(parentKey, children);
|
||||
|
||||
PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);
|
||||
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected virtual TItem MapRecycleBinViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
}
|
||||
|
||||
var viewModel = new TItem
|
||||
{
|
||||
Icon = _itemUdiType,
|
||||
Name = entity.Name!,
|
||||
Key = entity.Key,
|
||||
Type = _itemUdiType,
|
||||
HasChildren = entity.HasChildren,
|
||||
IsContainer = entity.IsContainer,
|
||||
ParentKey = parentKey
|
||||
};
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private IEntitySlim[] GetPagedRootEntities(long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
IEntitySlim[] rootEntities = _entityService
|
||||
.GetPagedTrashedChildren(RecycleBinRootId, ItemObjectType, pageNumber, pageSize, out totalItems)
|
||||
.ToArray();
|
||||
|
||||
return rootEntities;
|
||||
}
|
||||
|
||||
private IEntitySlim[] GetPagedChildEntities(Guid parentKey, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
IEntitySlim? parent = _entityService.Get(parentKey, ItemObjectType);
|
||||
if (parent == null || parent.Trashed == false)
|
||||
{
|
||||
// not much else we can do here but return nothing
|
||||
totalItems = 0;
|
||||
return Array.Empty<IEntitySlim>();
|
||||
}
|
||||
|
||||
IEntitySlim[] children = _entityService
|
||||
.GetPagedTrashedChildren(parent.Id, ItemObjectType, pageNumber, pageSize, out totalItems)
|
||||
.ToArray();
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
private TItem[] MapRecycleBinViewModels(Guid? parentKey, IEntitySlim[] entities)
|
||||
=> entities.Select(entity => MapRecycleBinViewModel(parentKey, entity)).ToArray();
|
||||
|
||||
private PagedViewModel<TItem> PagedViewModel(IEnumerable<TItem> treeItemViewModels, long totalItems)
|
||||
=> new() { Total = totalItems, Items = treeItemViewModels };
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.RelationType.Tree;
|
||||
|
||||
public class ItemsRelationTypeTreeController : RelationTypeTreeControllerBase
|
||||
{
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public ItemsRelationTypeTreeController(IEntityService entityService, IRelationService relationService)
|
||||
: base(entityService) =>
|
||||
_relationService = relationService;
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FolderTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FolderTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
{
|
||||
// relation service does not allow fetching a collection of relation types by their ids; instead it relies
|
||||
// heavily on caching, which means this is as fast as it gets - even if it looks less than performant
|
||||
IRelationType[] relationTypes = _relationService
|
||||
.GetAllRelationTypes()
|
||||
.Where(relationType => keys.Contains(relationType.Key)).ToArray();
|
||||
|
||||
EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(null, relationTypes);
|
||||
|
||||
return await Task.FromResult(Ok(viewModels));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.RelationType.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.RelationType}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.RelationType))]
|
||||
// NOTE: at the moment relation types aren't supported by EntityService, so we have little use of the
|
||||
// tree controller base. We'll keep it though, in the hope that we can mend EntityService.
|
||||
public class RelationTypeTreeControllerBase : EntityTreeControllerBase<EntityTreeItemViewModel>
|
||||
{
|
||||
public RelationTypeTreeControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.RelationType;
|
||||
|
||||
protected EntityTreeItemViewModel[] MapTreeItemViewModels(Guid? parentKey, IRelationType[] relationTypes)
|
||||
=> relationTypes.Select(relationType => new EntityTreeItemViewModel
|
||||
{
|
||||
Icon = Constants.Icons.RelationType,
|
||||
Name = relationType.Name!,
|
||||
Key = relationType.Key,
|
||||
Type = Constants.UdiEntityType.RelationType,
|
||||
HasChildren = false,
|
||||
IsContainer = false,
|
||||
ParentKey = parentKey
|
||||
}).ToArray();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.RelationType.Tree;
|
||||
|
||||
public class RootRelationTypeTreeController : RelationTypeTreeControllerBase
|
||||
{
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public RootRelationTypeTreeController(IEntityService entityService, IRelationService relationService)
|
||||
: base(entityService) =>
|
||||
_relationService = relationService;
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
// pagination is not supported (yet) by relation service, so we do it in memory for now
|
||||
// - chances are we won't have many relation types, so it won't be an actual issue
|
||||
IRelationType[] allRelationTypes = _relationService.GetAllRelationTypes().ToArray();
|
||||
|
||||
EntityTreeItemViewModel[] viewModels = MapTreeItemViewModels(
|
||||
null,
|
||||
allRelationTypes
|
||||
.OrderBy(relationType => relationType.Name)
|
||||
.Skip((int)(pageNumber * pageSize))
|
||||
.Take(pageSize)
|
||||
.ToArray());
|
||||
|
||||
PagedViewModel<EntityTreeItemViewModel> result = PagedViewModel(viewModels, allRelationTypes.Length);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Script.Tree;
|
||||
|
||||
public class ChildrenScriptTreeController : ScriptTreeControllerBase
|
||||
{
|
||||
public ChildrenScriptTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Children(string path, int skip = 0, int take = 100)
|
||||
=> await GetChildren(path, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Script.Tree;
|
||||
|
||||
public class ItemsScriptTreeController : ScriptTreeControllerBase
|
||||
{
|
||||
public ItemsScriptTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemViewModel>>> Items([FromQuery(Name = "path")] string[] paths)
|
||||
=> await GetItems(paths);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Script.Tree;
|
||||
|
||||
public class RootScriptTreeController : ScriptTreeControllerBase
|
||||
{
|
||||
public RootScriptTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Script.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Script}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Script))]
|
||||
public class ScriptTreeControllerBase : FileSystemTreeControllerBase
|
||||
{
|
||||
public ScriptTreeControllerBase(FileSystems fileSystems)
|
||||
=> FileSystem = fileSystems.ScriptsFileSystem ??
|
||||
throw new ArgumentException("Missing scripts file system", nameof(fileSystems));
|
||||
|
||||
protected override IFileSystem FileSystem { get; }
|
||||
|
||||
protected override string FileIcon(string path) => Constants.Icons.Script;
|
||||
|
||||
protected override string ItemType(string path) => Constants.UdiEntityType.Script;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using Umbraco.New.Cms.Web.Common.Routing;
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Server;
|
||||
|
||||
[ApiController]
|
||||
[BackOfficeRoute("api/v{version:apiVersion}/server")]
|
||||
[VersionedApiBackOfficeRoute("server")]
|
||||
[OpenApiTag("Server")]
|
||||
public abstract class ServerControllerBase : Controller
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.StaticFile.Tree;
|
||||
|
||||
public class ChildrenStaticFileTreeController : StaticFileTreeControllerBase
|
||||
{
|
||||
public ChildrenStaticFileTreeController(IPhysicalFileSystem physicalFileSystem)
|
||||
: base(physicalFileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Children(string path, int skip = 0, int take = 100)
|
||||
=> await GetChildren(path, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.StaticFile.Tree;
|
||||
|
||||
public class ItemsStaticFileTreeController : StaticFileTreeControllerBase
|
||||
{
|
||||
public ItemsStaticFileTreeController(IPhysicalFileSystem physicalFileSystem)
|
||||
: base(physicalFileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemViewModel>>> Items([FromQuery(Name = "path")] string[] paths)
|
||||
=> await GetItems(paths);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.StaticFile.Tree;
|
||||
|
||||
public class RootStaticFileTreeController : StaticFileTreeControllerBase
|
||||
{
|
||||
public RootStaticFileTreeController(IPhysicalFileSystem physicalFileSystem)
|
||||
: base(physicalFileSystem)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.StaticFile.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute("static-file/tree")]
|
||||
[OpenApiTag("StaticFile")]
|
||||
public class StaticFileTreeControllerBase : FileSystemTreeControllerBase
|
||||
{
|
||||
private static readonly string[] _allowedRootFolders = { "App_Plugins", "wwwroot" };
|
||||
|
||||
public StaticFileTreeControllerBase(IPhysicalFileSystem physicalFileSystem)
|
||||
=> FileSystem = physicalFileSystem;
|
||||
|
||||
protected override IFileSystem FileSystem { get; }
|
||||
|
||||
protected override string FileIcon(string path) => Constants.Icons.DefaultIcon;
|
||||
|
||||
protected override string ItemType(string path) => "static-file";
|
||||
|
||||
protected override string[] GetDirectories(string path) =>
|
||||
IsTreeRootPath(path)
|
||||
? _allowedRootFolders
|
||||
: IsAllowedPath(path)
|
||||
? base.GetDirectories(path)
|
||||
: Array.Empty<string>();
|
||||
|
||||
protected override string[] GetFiles(string path)
|
||||
=> IsTreeRootPath(path) || IsAllowedPath(path) == false
|
||||
? Array.Empty<string>()
|
||||
: base.GetFiles(path);
|
||||
|
||||
private bool IsTreeRootPath(string path) => string.IsNullOrWhiteSpace(path);
|
||||
|
||||
private bool IsAllowedPath(string path) => _allowedRootFolders.Contains(path) || _allowedRootFolders.Any(folder => path.StartsWith($"{folder}/"));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Stylesheet.Tree;
|
||||
|
||||
public class ChildrenStylesheetTreeController : StylesheetTreeControllerBase
|
||||
{
|
||||
public ChildrenStylesheetTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Children(string path, int skip = 0, int take = 100)
|
||||
=> await GetChildren(path, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Stylesheet.Tree;
|
||||
|
||||
public class ItemsStylesheetTreeController : StylesheetTreeControllerBase
|
||||
{
|
||||
public ItemsStylesheetTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<FileSystemTreeItemViewModel>>> Items([FromQuery(Name = "path")] string[] paths)
|
||||
=> await GetItems(paths);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Stylesheet.Tree;
|
||||
|
||||
public class RootStylesheetTreeController : StylesheetTreeControllerBase
|
||||
{
|
||||
public RootStylesheetTreeController(FileSystems fileSystems)
|
||||
: base(fileSystems)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<FileSystemTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Stylesheet.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Stylesheet))]
|
||||
public class StylesheetTreeControllerBase : FileSystemTreeControllerBase
|
||||
{
|
||||
public StylesheetTreeControllerBase(FileSystems fileSystems)
|
||||
=> FileSystem = fileSystems.StylesheetsFileSystem ??
|
||||
throw new ArgumentException("Missing stylesheets file system", nameof(fileSystems));
|
||||
|
||||
protected override IFileSystem FileSystem { get; }
|
||||
|
||||
protected override string FileIcon(string path) => Constants.Icons.Stylesheet;
|
||||
|
||||
protected override string ItemType(string path) => Constants.UdiEntityType.Stylesheet;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Template.Tree;
|
||||
|
||||
public class ChildrenTemplateTreeController : TemplateTreeControllerBase
|
||||
{
|
||||
public ChildrenTemplateTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("children")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Children(Guid parentKey, int skip = 0, int take = 100)
|
||||
=> await GetChildren(parentKey, skip, take);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Template.Tree;
|
||||
|
||||
public class ItemsTemplateTreeController : TemplateTreeControllerBase
|
||||
{
|
||||
public ItemsTemplateTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("items")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(IEnumerable<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<EntityTreeItemViewModel>>> Items([FromQuery(Name = "key")] Guid[] keys)
|
||||
=> await GetItems(keys);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Template.Tree;
|
||||
|
||||
public class RootTemplateTreeController : TemplateTreeControllerBase
|
||||
{
|
||||
public RootTemplateTreeController(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpGet("root")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<EntityTreeItemViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PagedViewModel<EntityTreeItemViewModel>>> Root(int skip = 0, int take = 100)
|
||||
=> await GetRoot(skip, take);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSwag.Annotations;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Template.Tree;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[ApiController]
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Template}/tree")]
|
||||
[OpenApiTag(nameof(Constants.UdiEntityType.Template))]
|
||||
public class TemplateTreeControllerBase : EntityTreeControllerBase<EntityTreeItemViewModel>
|
||||
{
|
||||
public TemplateTreeControllerBase(IEntityService entityService)
|
||||
: base(entityService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.Template;
|
||||
|
||||
protected override EntityTreeItemViewModel MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
EntityTreeItemViewModel viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
viewModel.Icon = Constants.Icons.Template;
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
|
||||
public abstract class EntityTreeControllerBase<TItem> : ManagementApiControllerBase
|
||||
where TItem : EntityTreeItemViewModel, new()
|
||||
{
|
||||
private readonly string _itemUdiType;
|
||||
|
||||
protected EntityTreeControllerBase(IEntityService entityService)
|
||||
{
|
||||
EntityService = entityService;
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_itemUdiType = ItemObjectType.GetUdiType();
|
||||
}
|
||||
|
||||
protected IEntityService EntityService { get; }
|
||||
|
||||
protected abstract UmbracoObjectTypes ItemObjectType { get; }
|
||||
|
||||
protected virtual Ordering ItemOrdering => Ordering.By(nameof(Infrastructure.Persistence.Dtos.NodeDto.Text));
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<TItem>>> GetRoot(int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IEntitySlim[] rootEntities = GetPagedRootEntities(pageNumber, pageSize, out var totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(null, rootEntities);
|
||||
|
||||
PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentKey, int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
IEntitySlim[] children = GetPagedChildEntities(parentKey, pageNumber, pageSize, out var totalItems);
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(parentKey, children);
|
||||
|
||||
PagedViewModel<TItem> result = PagedViewModel(treeItemViewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<IEnumerable<TItem>>> GetItems(Guid[] keys)
|
||||
{
|
||||
if (keys.IsCollectionEmpty())
|
||||
{
|
||||
return await Task.FromResult(Ok(PagedViewModel(Array.Empty<TItem>(), 0)));
|
||||
}
|
||||
|
||||
IEntitySlim[] itemEntities = GetEntities(keys);
|
||||
|
||||
TItem[] treeItemViewModels = MapTreeItemViewModels(null, itemEntities);
|
||||
|
||||
return await Task.FromResult(Ok(treeItemViewModels));
|
||||
}
|
||||
|
||||
protected virtual IEntitySlim[] GetPagedRootEntities(long pageNumber, int pageSize, out long totalItems)
|
||||
=> EntityService
|
||||
.GetPagedChildren(
|
||||
Constants.System.Root,
|
||||
ItemObjectType,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
out totalItems,
|
||||
ordering: ItemOrdering)
|
||||
.ToArray();
|
||||
|
||||
protected virtual IEntitySlim[] GetPagedChildEntities(Guid parentKey, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
// EntityService is only able to get paged children by parent ID, so we must first map parent key to parent ID
|
||||
Attempt<int> parentId = EntityService.GetId(parentKey, ItemObjectType);
|
||||
if (parentId.Success == false)
|
||||
{
|
||||
// not much else we can do here but return nothing
|
||||
totalItems = 0;
|
||||
return Array.Empty<IEntitySlim>();
|
||||
}
|
||||
|
||||
IEntitySlim[] children = EntityService.GetPagedChildren(
|
||||
parentId.Result,
|
||||
ItemObjectType,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
out totalItems,
|
||||
ordering: ItemOrdering)
|
||||
.ToArray();
|
||||
return children;
|
||||
}
|
||||
|
||||
protected virtual IEntitySlim[] GetEntities(Guid[] keys) => EntityService.GetAll(ItemObjectType, keys).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
|
||||
{
|
||||
Icon = _itemUdiType,
|
||||
Name = entity.Name!,
|
||||
Key = entity.Key,
|
||||
Type = _itemUdiType,
|
||||
HasChildren = entity.HasChildren,
|
||||
IsContainer = entity.IsContainer,
|
||||
ParentKey = parentKey
|
||||
};
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
protected PagedViewModel<TItem> PagedViewModel(IEnumerable<TItem> treeItemViewModels, long totalItems)
|
||||
=> new() { Total = totalItems, Items = treeItemViewModels };
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Pagination;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
|
||||
public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected abstract IFileSystem FileSystem { get; }
|
||||
|
||||
protected abstract string FileIcon(string path);
|
||||
|
||||
protected abstract string ItemType(string path);
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> GetRoot(int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
FileSystemTreeItemViewModel[] viewModels = GetPathViewModels(string.Empty, pageNumber, pageSize, out var totalItems);
|
||||
|
||||
PagedViewModel<FileSystemTreeItemViewModel> result = PagedViewModel(viewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<PagedViewModel<FileSystemTreeItemViewModel>>> GetChildren(string path, int skip, int take)
|
||||
{
|
||||
if (PaginationService.ConvertSkipTakeToPaging(skip, take, out var pageNumber, out var pageSize, out ProblemDetails? error) == false)
|
||||
{
|
||||
return BadRequest(error);
|
||||
}
|
||||
|
||||
FileSystemTreeItemViewModel[] viewModels = GetPathViewModels(path, pageNumber, pageSize, out var totalItems);
|
||||
|
||||
PagedViewModel<FileSystemTreeItemViewModel> result = PagedViewModel(viewModels, totalItems);
|
||||
return await Task.FromResult(Ok(result));
|
||||
}
|
||||
|
||||
protected async Task<ActionResult<IEnumerable<FileSystemTreeItemViewModel>>> GetItems(string[] paths)
|
||||
{
|
||||
FileSystemTreeItemViewModel[] viewModels = paths
|
||||
.Where(FileSystem.FileExists)
|
||||
.Select(path =>
|
||||
{
|
||||
var fileName = GetFileName(path);
|
||||
return fileName.IsNullOrWhiteSpace()
|
||||
? null
|
||||
: MapViewModel(path, fileName, false);
|
||||
}).WhereNotNull().ToArray();
|
||||
|
||||
return await Task.FromResult(Ok(viewModels));
|
||||
}
|
||||
|
||||
protected virtual string[] GetDirectories(string path) => FileSystem
|
||||
.GetDirectories(path)
|
||||
.OrderBy(directory => directory)
|
||||
.ToArray();
|
||||
|
||||
protected virtual string[] GetFiles(string path) => FileSystem
|
||||
.GetFiles(path)
|
||||
.OrderBy(file => file)
|
||||
.ToArray();
|
||||
|
||||
protected virtual string GetFileName(string path) => FileSystem.GetFileName(path);
|
||||
|
||||
protected virtual bool DirectoryHasChildren(string path)
|
||||
=> FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any();
|
||||
|
||||
private FileSystemTreeItemViewModel[] GetPathViewModels(string path, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
var allItems = GetDirectories(path)
|
||||
.Select(directory => new { Path = directory, IsFolder = true })
|
||||
.Union(GetFiles(path).Select(file => new { Path = file, IsFolder = false }))
|
||||
.ToArray();
|
||||
|
||||
totalItems = allItems.Length;
|
||||
|
||||
FileSystemTreeItemViewModel ViewModel(string itemPath, bool isFolder)
|
||||
=> MapViewModel(
|
||||
itemPath,
|
||||
isFolder ? Path.GetFileName(itemPath) : FileSystem.GetFileName(itemPath),
|
||||
isFolder);
|
||||
|
||||
return allItems
|
||||
.Skip((int)(pageNumber * pageSize))
|
||||
.Take(pageSize)
|
||||
.Select(item => ViewModel(item.Path, item.IsFolder))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private PagedViewModel<FileSystemTreeItemViewModel> PagedViewModel(IEnumerable<FileSystemTreeItemViewModel> viewModels, long totalItems)
|
||||
=> new() { Total = totalItems, Items = viewModels };
|
||||
|
||||
private FileSystemTreeItemViewModel MapViewModel(string path, string name, bool isFolder)
|
||||
=> new()
|
||||
{
|
||||
Path = path,
|
||||
Name = name,
|
||||
Icon = isFolder ? Constants.Icons.Folder : FileIcon(path),
|
||||
HasChildren = isFolder && DirectoryHasChildren(path),
|
||||
Type = ItemType(path),
|
||||
IsFolder = isFolder
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
|
||||
public abstract class FolderTreeControllerBase<TItem> : EntityTreeControllerBase<TItem>
|
||||
where TItem : FolderTreeItemViewModel, new()
|
||||
{
|
||||
private readonly Guid _folderObjectTypeId;
|
||||
private bool _foldersOnly;
|
||||
|
||||
protected FolderTreeControllerBase(IEntityService entityService)
|
||||
: base(entityService) =>
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
_folderObjectTypeId = FolderObjectType.GetGuid();
|
||||
|
||||
protected abstract UmbracoObjectTypes FolderObjectType { get; }
|
||||
|
||||
protected void RenderFoldersOnly(bool foldersOnly) => _foldersOnly = foldersOnly;
|
||||
|
||||
protected override IEntitySlim[] GetPagedRootEntities(long pageNumber, int pageSize, out long totalItems)
|
||||
=> GetEntities(
|
||||
Constants.System.Root,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
out totalItems);
|
||||
|
||||
protected override IEntitySlim[] GetPagedChildEntities(Guid parentKey, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
// EntityService is only able to get paged children by parent ID, so we must first map parent key to parent ID
|
||||
Attempt<int> parentId = EntityService.GetId(parentKey, FolderObjectType);
|
||||
if (parentId.Success == false)
|
||||
{
|
||||
parentId = EntityService.GetId(parentKey, ItemObjectType);
|
||||
if (parentId.Success == false)
|
||||
{
|
||||
// not much else we can do here but return nothing
|
||||
totalItems = 0;
|
||||
return Array.Empty<IEntitySlim>();
|
||||
}
|
||||
}
|
||||
|
||||
return GetEntities(
|
||||
parentId.Result,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
out totalItems);
|
||||
}
|
||||
|
||||
protected override TItem MapTreeItemViewModel(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
TItem viewModel = base.MapTreeItemViewModel(parentKey, entity);
|
||||
|
||||
if (entity.NodeObjectType == _folderObjectTypeId)
|
||||
{
|
||||
viewModel.IsFolder = true;
|
||||
viewModel.Icon = Constants.Icons.Folder;
|
||||
}
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private IEntitySlim[] GetEntities(int parentId, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
totalItems = 0;
|
||||
|
||||
// EntityService is not able to paginate children of multiple item types, so we will only paginate the
|
||||
// item type entities and always return all folders as part of the the first result page
|
||||
IEntitySlim[] folderEntities = pageNumber == 0
|
||||
? EntityService.GetChildren(parentId, FolderObjectType).OrderBy(c => c.Name).ToArray()
|
||||
: Array.Empty<IEntitySlim>();
|
||||
IEntitySlim[] itemEntities = _foldersOnly
|
||||
? Array.Empty<IEntitySlim>()
|
||||
: EntityService.GetPagedChildren(
|
||||
parentId,
|
||||
ItemObjectType,
|
||||
pageNumber,
|
||||
pageSize,
|
||||
out totalItems,
|
||||
ordering: ItemOrdering)
|
||||
.ToArray();
|
||||
|
||||
return folderEntities.Union(itemEntities).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Models.Entities;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Controllers.Tree;
|
||||
|
||||
public abstract class UserStartNodeTreeControllerBase<TItem> : EntityTreeControllerBase<TItem>
|
||||
where TItem : ContentTreeItemViewModel, new()
|
||||
{
|
||||
private readonly IUserStartNodeEntitiesService _userStartNodeEntitiesService;
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
|
||||
private int[]? _userStartNodeIds;
|
||||
private string[]? _userStartNodePaths;
|
||||
private Dictionary<Guid, bool> _accessMap = new();
|
||||
private Guid? _dataTypeKey;
|
||||
|
||||
protected UserStartNodeTreeControllerBase(
|
||||
IEntityService entityService,
|
||||
IUserStartNodeEntitiesService userStartNodeEntitiesService,
|
||||
IDataTypeService dataTypeService)
|
||||
: base(entityService)
|
||||
{
|
||||
_userStartNodeEntitiesService = userStartNodeEntitiesService;
|
||||
_dataTypeService = dataTypeService;
|
||||
}
|
||||
|
||||
protected abstract int[] GetUserStartNodeIds();
|
||||
|
||||
protected abstract string[] GetUserStartNodePaths();
|
||||
|
||||
protected void IgnoreUserStartNodesForDataType(Guid? dataTypeKey) => _dataTypeKey = dataTypeKey;
|
||||
|
||||
protected override IEntitySlim[] GetPagedRootEntities(long pageNumber, int pageSize, out long totalItems)
|
||||
=> UserHasRootAccess() || IgnoreUserStartNodes()
|
||||
? base.GetPagedRootEntities(pageNumber, pageSize, out totalItems)
|
||||
: CalculateAccessMap(() => _userStartNodeEntitiesService.RootUserAccessEntities(ItemObjectType, UserStartNodeIds), out totalItems);
|
||||
|
||||
protected override IEntitySlim[] GetPagedChildEntities(Guid parentKey, long pageNumber, int pageSize, out long totalItems)
|
||||
{
|
||||
IEntitySlim[] children = base.GetPagedChildEntities(parentKey, pageNumber, pageSize, out totalItems);
|
||||
return UserHasRootAccess() || IgnoreUserStartNodes()
|
||||
? children
|
||||
: 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())
|
||||
{
|
||||
return base.MapTreeItemViewModels(parentKey, entities);
|
||||
}
|
||||
|
||||
// for users with no root access, only add items for the entities contained within the calculated access map.
|
||||
// the access map may contain entities that the user does not have direct access to, but need still to see,
|
||||
// because it has descendants that the user *does* have access to. these entities are added as "no access" items.
|
||||
TItem[] contentTreeItemViewModels = entities.Select(entity =>
|
||||
{
|
||||
if (_accessMap.TryGetValue(entity.Key, out var hasAccess) == false)
|
||||
{
|
||||
// entity is not a part of the calculated access map
|
||||
return null;
|
||||
}
|
||||
|
||||
// direct access => return a regular item
|
||||
// no direct access => return a "no access" item
|
||||
return hasAccess
|
||||
? MapTreeItemViewModel(parentKey, entity)
|
||||
: MapTreeItemViewModelAsNoAccess(parentKey, entity);
|
||||
})
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
|
||||
return contentTreeItemViewModels;
|
||||
}
|
||||
|
||||
private int[] UserStartNodeIds => _userStartNodeIds ??= GetUserStartNodeIds();
|
||||
|
||||
private string[] UserStartNodePaths => _userStartNodePaths ??= GetUserStartNodePaths();
|
||||
|
||||
private bool UserHasRootAccess() => UserStartNodeIds.Contains(Constants.System.Root);
|
||||
|
||||
private bool IgnoreUserStartNodes()
|
||||
=> _dataTypeKey.HasValue
|
||||
&& _dataTypeService.IsDataTypeIgnoringUserStartNodes(_dataTypeKey.Value);
|
||||
|
||||
private IEntitySlim[] CalculateAccessMap(Func<IEnumerable<UserAccessEntity>> getUserAccessEntities, out long totalItems)
|
||||
{
|
||||
UserAccessEntity[] userAccessEntities = getUserAccessEntities().ToArray();
|
||||
|
||||
_accessMap = userAccessEntities.ToDictionary(uae => uae.Entity.Key, uae => uae.HasAccess);
|
||||
|
||||
IEntitySlim[] entities = userAccessEntities.Select(uae => uae.Entity).ToArray();
|
||||
totalItems = entities.Length;
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private TItem MapTreeItemViewModelAsNoAccess(Guid? parentKey, IEntitySlim entity)
|
||||
{
|
||||
TItem viewModel = MapTreeItemViewModel(parentKey, entity);
|
||||
viewModel.NoAccess = true;
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Cms.ManagementApi.Controllers.Upgrade;
|
||||
|
||||
[ApiController]
|
||||
[RequireRuntimeLevel(RuntimeLevel.Upgrade)]
|
||||
[BackOfficeRoute("api/v{version:apiVersion}/upgrade")]
|
||||
[VersionedApiBackOfficeRoute("upgrade")]
|
||||
[OpenApiTag("Upgrade")]
|
||||
public abstract class UpgradeControllerBase : Controller
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.ManagementApi.Mapping.Installer;
|
||||
using Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
using Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
using Umbraco.New.Cms.Core.Factories;
|
||||
using Umbraco.New.Cms.Core.Installer;
|
||||
using Umbraco.New.Cms.Core.Installer.Steps;
|
||||
@@ -76,4 +78,10 @@ public static class InstallerBuilderExtensions
|
||||
|
||||
public static UpgradeStepCollectionBuilder UpgradeSteps(this IUmbracoBuilder builder)
|
||||
=> builder.WithCollectionBuilder<UpgradeStepCollectionBuilder>();
|
||||
|
||||
internal static IUmbracoBuilder AddTrees(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddTransient<IUserStartNodeEntitiesService, UserStartNodeEntitiesService>();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Filters;
|
||||
|
||||
public class RequireDocumentTreeRootAccessAttribute : RequireTreeRootAccessAttribute
|
||||
{
|
||||
protected override int[] GetUserStartNodeIds(IUser user, ActionExecutingContext context)
|
||||
{
|
||||
AppCaches appCaches = context.HttpContext.RequestServices.GetRequiredService<AppCaches>();
|
||||
IEntityService entityService = context.HttpContext.RequestServices.GetRequiredService<IEntityService>();
|
||||
|
||||
return user.CalculateContentStartNodeIds(entityService, appCaches) ?? Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Filters;
|
||||
|
||||
public class RequireMediaTreeRootAccessAttribute : RequireTreeRootAccessAttribute
|
||||
{
|
||||
protected override int[] GetUserStartNodeIds(IUser user, ActionExecutingContext context)
|
||||
{
|
||||
AppCaches appCaches = context.HttpContext.RequestServices.GetRequiredService<AppCaches>();
|
||||
IEntityService entityService = context.HttpContext.RequestServices.GetRequiredService<IEntityService>();
|
||||
|
||||
return user.CalculateMediaStartNodeIds(entityService, appCaches) ?? Array.Empty<int>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Filters;
|
||||
|
||||
public abstract class RequireTreeRootAccessAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor = context.HttpContext.RequestServices.GetRequiredService<IBackOfficeSecurityAccessor>();
|
||||
IUser? user = backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
||||
|
||||
var startNodeIds = user != null ? GetUserStartNodeIds(user, context) : Array.Empty<int>();
|
||||
|
||||
// TODO: remove this once we have backoffice auth in place
|
||||
startNodeIds = new[] { Constants.System.Root };
|
||||
|
||||
if (startNodeIds.Contains(Constants.System.Root))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Title = "Unauthorized user",
|
||||
Detail = "The current backoffice user should have access to the tree root",
|
||||
Status = StatusCodes.Status401Unauthorized,
|
||||
Type = "Error",
|
||||
};
|
||||
|
||||
context.Result = new ObjectResult(problemDetails) { StatusCode = StatusCodes.Status401Unauthorized };
|
||||
}
|
||||
|
||||
protected abstract int[] GetUserStartNodeIds(IUser user, ActionExecutingContext context);
|
||||
}
|
||||
@@ -36,7 +36,8 @@ public class ManagementApiComposer : IComposer
|
||||
builder
|
||||
.AddNewInstaller()
|
||||
.AddUpgrader()
|
||||
.AddExamineManagement();
|
||||
.AddExamineManagement()
|
||||
.AddTrees();
|
||||
|
||||
services.AddApiVersioning(options =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Models.Entities;
|
||||
|
||||
public class UserAccessEntity
|
||||
{
|
||||
public UserAccessEntity(IEntitySlim entity, bool hasAccess)
|
||||
{
|
||||
Entity = entity;
|
||||
HasAccess = hasAccess;
|
||||
}
|
||||
|
||||
public IEntitySlim Entity { get; }
|
||||
|
||||
public bool HasAccess { get; }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
|
||||
public interface IUserStartNodeEntitiesService
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the applicable root entities for a given object type for users without root access.
|
||||
/// </summary>
|
||||
/// <param name="umbracoObjectType">The object type.</param>
|
||||
/// <param name="userStartNodeIds">The calculated start node IDs for the user.</param>
|
||||
/// <returns>A list of root entities for the user.</returns>
|
||||
/// <remarks>
|
||||
/// The returned entities may include entities that outside of the user start node scope, but are needed to
|
||||
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
|
||||
/// </remarks>
|
||||
IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the applicable child entities from a list of candidate child entities for users without root access.
|
||||
/// </summary>
|
||||
/// <param name="candidateChildren">The candidate child entities to filter (i.e. entities fetched with <see cref="EntityService.GetPagedChildren"/>).</param>
|
||||
/// <param name="userStartNodePaths">The calculated start node paths for the user.</param>
|
||||
/// <returns>A list of child entities applicable entities for the user.</returns>
|
||||
/// <remarks>
|
||||
/// The returned entities may include entities that outside of the user start node scope, but are needed to
|
||||
/// for browsing to the actual user start nodes. These entities will be marked as "no access" entities.
|
||||
/// Some candidate entities may be filtered out if they are not applicable for the user scope.
|
||||
/// </remarks>
|
||||
IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntitySlim> candidateChildren, string[] userStartNodePaths);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the access level of a collection of entities for users without root access.
|
||||
/// </summary>
|
||||
/// <param name="entities">The entities.</param>
|
||||
/// <param name="userStartNodePaths">The calculated start node paths for the user.</param>
|
||||
/// <returns> The access level for each entity.</returns>
|
||||
IEnumerable<UserAccessEntity> UserAccessEntities(IEnumerable<IEntitySlim> entities, string[] userStartNodePaths);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.ManagementApi.Models.Entities;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Services.Entities;
|
||||
|
||||
public class UserStartNodeEntitiesService : IUserStartNodeEntitiesService
|
||||
{
|
||||
private readonly IEntityService _entityService;
|
||||
|
||||
public UserStartNodeEntitiesService(IEntityService entityService) => _entityService = entityService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UserAccessEntity> RootUserAccessEntities(UmbracoObjectTypes umbracoObjectType, int[] userStartNodeIds)
|
||||
{
|
||||
// root entities for users without root access should include:
|
||||
// - the start nodes that are actual root entities (level == 1)
|
||||
// - the root level ancestors to the rest of the start nodes (required for browsing to the actual start nodes - will be marked as "no access")
|
||||
IEntitySlim[] userStartEntities = _entityService.GetAll(umbracoObjectType, userStartNodeIds).ToArray();
|
||||
|
||||
// find the start nodes that are at root level (level == 1)
|
||||
IEntitySlim[] allowedTopmostEntities = userStartEntities.Where(entity => entity.Level == 1).ToArray();
|
||||
|
||||
// find the root level ancestors of the rest of the start nodes, and add those as well
|
||||
var nonAllowedTopmostEntityIds = userStartEntities.Except(allowedTopmostEntities)
|
||||
.Select(entity => int.TryParse(entity.Path.Split(Constants.CharArrays.Comma).Skip(1).FirstOrDefault(), out var id) ? id : 0)
|
||||
.Where(id => id > 0)
|
||||
.ToArray();
|
||||
IEntitySlim[] nonAllowedTopmostEntities = nonAllowedTopmostEntityIds.Any()
|
||||
? _entityService.GetAll(umbracoObjectType, nonAllowedTopmostEntityIds).ToArray()
|
||||
: Array.Empty<IEntitySlim>();
|
||||
|
||||
return allowedTopmostEntities
|
||||
.Select(entity => new UserAccessEntity(entity, true))
|
||||
.Union(
|
||||
nonAllowedTopmostEntities
|
||||
.Select(entity => new UserAccessEntity(entity, false)))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UserAccessEntity> ChildUserAccessEntities(IEnumerable<IEntitySlim> candidateChildren, string[] userStartNodePaths)
|
||||
// child entities for users without root access should include:
|
||||
// - children that are descendant-or-self of a user start node
|
||||
// - children that are ancestors of a user start node (required for browsing to the actual start nodes - will be marked as "no access")
|
||||
// all other candidate children should be discarded
|
||||
=> candidateChildren.Select(child =>
|
||||
{
|
||||
// is descendant-or-self of a start node?
|
||||
if (IsDescendantOrSelf(child, userStartNodePaths))
|
||||
{
|
||||
return new UserAccessEntity(child, true);
|
||||
}
|
||||
|
||||
// is ancestor of a start node?
|
||||
if (userStartNodePaths.Any(path => path.StartsWith(child.Path)))
|
||||
{
|
||||
return new UserAccessEntity(child, false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}).WhereNotNull().ToArray();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<UserAccessEntity> UserAccessEntities(IEnumerable<IEntitySlim> entities, string[] userStartNodePaths)
|
||||
// entities for users without root access should include:
|
||||
// - entities that are descendant-or-self of a user start node as regular entities
|
||||
// - all other entities as "no access" entities
|
||||
=> entities.Select(entity => new UserAccessEntity(entity, IsDescendantOrSelf(entity, userStartNodePaths))).ToArray();
|
||||
|
||||
private static bool IsDescendantOrSelf(IEntitySlim child, string[] userStartNodePaths)
|
||||
=> userStartNodePaths.Any(path => child.Path.StartsWith(path));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Umbraco.Cms.ManagementApi.Services.Paging;
|
||||
|
||||
// TODO: remove this class once EF core is in place with proper skip/take pagination implementation
|
||||
// this service is used for converting skip/take to classic pagination with page number and page size.
|
||||
// it is a temporary solution that should be removed once EF core is in place, thus we'll live
|
||||
// with this code being statically referenced across multiple controllers. the alternative would be
|
||||
// an injectable service, but that would require a greater clean-up effort later on.
|
||||
internal static class PaginationService
|
||||
{
|
||||
internal static bool ConvertSkipTakeToPaging(int skip, int take, out long pageNumber, out int pageSize, out ProblemDetails? error)
|
||||
{
|
||||
if (take <= 0)
|
||||
{
|
||||
throw new ArgumentException("Must be greater than zero", nameof(take));
|
||||
}
|
||||
|
||||
if (skip % take != 0)
|
||||
{
|
||||
pageSize = 0;
|
||||
pageNumber = 0;
|
||||
error = new ProblemDetails
|
||||
{
|
||||
Title = "Invalid skip/take",
|
||||
Detail = "Skip must be a multiple of take - i.e. skip = 10, take = 5",
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "Error",
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
pageSize = take;
|
||||
pageNumber = skip / take;
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.RecycleBin;
|
||||
|
||||
public class RecycleBinItemViewModel
|
||||
{
|
||||
public Guid Key { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
|
||||
public bool HasChildren { get; set; }
|
||||
|
||||
public bool IsContainer { get; set; }
|
||||
|
||||
public Guid? ParentKey { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class ContentTreeItemViewModel : EntityTreeItemViewModel
|
||||
{
|
||||
public bool NoAccess { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class DocumentBlueprintTreeItemViewModel : EntityTreeItemViewModel
|
||||
{
|
||||
public Guid DocumentTypeKey { get; set; }
|
||||
|
||||
public string DocumentTypeAlias { get; set; } = string.Empty;
|
||||
|
||||
public string? DocumentTypeName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class DocumentTreeItemViewModel : ContentTreeItemViewModel
|
||||
{
|
||||
public bool IsProtected { get; set; }
|
||||
|
||||
public bool IsPublished { get; set; }
|
||||
|
||||
public bool IsEdited { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class DocumentTypeTreeItemViewModel : FolderTreeItemViewModel
|
||||
{
|
||||
public bool IsElement { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class EntityTreeItemViewModel : TreeItemViewModel
|
||||
{
|
||||
public Guid Key { get; set; }
|
||||
|
||||
public bool IsContainer { get; set; }
|
||||
|
||||
public Guid? ParentKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class FileSystemTreeItemViewModel : TreeItemViewModel
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
|
||||
public bool IsFolder { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class FolderTreeItemViewModel : EntityTreeItemViewModel
|
||||
{
|
||||
public bool IsFolder { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.ManagementApi.ViewModels.Tree;
|
||||
|
||||
public class TreeItemViewModel
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
public string Icon { get; set; } = string.Empty;
|
||||
|
||||
public bool HasChildren { get; set; }
|
||||
}
|
||||
@@ -119,11 +119,31 @@ public static partial class Constants
|
||||
/// </summary>
|
||||
public const string Packages = "icon-box";
|
||||
|
||||
/// <summary>
|
||||
/// System property editor icon
|
||||
/// </summary>
|
||||
public const string PartialView = "icon-article";
|
||||
|
||||
/// <summary>
|
||||
/// System property editor icon
|
||||
/// </summary>
|
||||
public const string PropertyEditor = "icon-autofill";
|
||||
|
||||
/// <summary>
|
||||
/// Relation type icon
|
||||
/// </summary>
|
||||
public const string RelationType = "icon-trafic";
|
||||
|
||||
/// <summary>
|
||||
/// Script type icon
|
||||
/// </summary>
|
||||
public const string Script = "icon-script";
|
||||
|
||||
/// <summary>
|
||||
/// Stylesheet type icon
|
||||
/// </summary>
|
||||
public const string Stylesheet = "icon-brackets";
|
||||
|
||||
/// <summary>
|
||||
/// System member icon
|
||||
/// </summary>
|
||||
|
||||
@@ -54,7 +54,7 @@ public enum UmbracoObjectTypes
|
||||
/// <summary>
|
||||
/// Member Group
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup)]
|
||||
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup, typeof(IMemberGroup))]
|
||||
[FriendlyName("Member Group")]
|
||||
[UmbracoUdiType(Constants.UdiEntityType.MemberGroup)]
|
||||
MemberGroup,
|
||||
|
||||
@@ -6,6 +6,10 @@ public interface IDictionaryRepository : IReadWriteQueryRepository<int, IDiction
|
||||
{
|
||||
IDictionaryItem? Get(Guid uniqueId);
|
||||
|
||||
IEnumerable<IDictionaryItem> GetMany(params Guid[] uniqueIds) => Array.Empty<IDictionaryItem>();
|
||||
|
||||
IEnumerable<IDictionaryItem> GetManyByKeys(params string[] keys) => Array.Empty<IDictionaryItem>();
|
||||
|
||||
IDictionaryItem? Get(string key);
|
||||
|
||||
IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId);
|
||||
|
||||
@@ -37,6 +37,8 @@ public class EntityService : RepositoryService, IEntityService
|
||||
{ typeof(IMediaType).FullName!, UmbracoObjectTypes.MediaType },
|
||||
{ typeof(IMember).FullName!, UmbracoObjectTypes.Member },
|
||||
{ typeof(IMemberType).FullName!, UmbracoObjectTypes.MemberType },
|
||||
{ typeof(IMemberGroup).FullName!, UmbracoObjectTypes.MemberGroup },
|
||||
{ typeof(ITemplate).FullName!, UmbracoObjectTypes.Template },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -314,14 +316,18 @@ public class EntityService : RepositoryService, IEntityService
|
||||
out long totalRecords,
|
||||
IQuery<IUmbracoEntity>? filter = null,
|
||||
Ordering? ordering = null)
|
||||
{
|
||||
using (ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == false);
|
||||
=> GetPagedChildren(id, objectType, pageIndex, pageSize, false, filter, ordering, out totalRecords);
|
||||
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedTrashedChildren(
|
||||
int id,
|
||||
UmbracoObjectTypes objectType,
|
||||
long pageIndex,
|
||||
int pageSize,
|
||||
out long totalRecords,
|
||||
IQuery<IUmbracoEntity>? filter = null,
|
||||
Ordering? ordering = null)
|
||||
=> GetPagedChildren(id, objectType, pageIndex, pageSize, true, filter, ordering, out totalRecords);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntitySlim> GetPagedDescendants(
|
||||
@@ -523,4 +529,23 @@ public class EntityService : RepositoryService, IEntityService
|
||||
|
||||
return objType;
|
||||
}
|
||||
|
||||
private IEnumerable<IEntitySlim> GetPagedChildren(
|
||||
int id,
|
||||
UmbracoObjectTypes objectType,
|
||||
long pageIndex,
|
||||
int pageSize,
|
||||
bool trashed,
|
||||
IQuery<IUmbracoEntity>? filter,
|
||||
Ordering? ordering,
|
||||
out long totalRecords)
|
||||
{
|
||||
using (ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
IQuery<IUmbracoEntity> query = Query<IUmbracoEntity>().Where(x => x.ParentId == id && x.Trashed == trashed);
|
||||
|
||||
return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,22 @@ public interface IEntityService
|
||||
IQuery<IUmbracoEntity>? filter = null,
|
||||
Ordering? ordering = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets children of an entity.
|
||||
/// </summary>
|
||||
IEnumerable<IEntitySlim> GetPagedTrashedChildren(
|
||||
int id,
|
||||
UmbracoObjectTypes objectType,
|
||||
long pageIndex,
|
||||
int pageSize,
|
||||
out long totalRecords,
|
||||
IQuery<IUmbracoEntity>? filter = null,
|
||||
Ordering? ordering = null)
|
||||
{
|
||||
totalRecords = 0;
|
||||
return Array.Empty<IEntitySlim>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets descendants of an entity.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,6 +49,15 @@ public interface ILocalizationService : IService
|
||||
/// </returns>
|
||||
IDictionaryItem? GetDictionaryItemById(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IDictionaryItem" /> by their <see cref="Guid" /> ids
|
||||
/// </summary>
|
||||
/// <param name="ids">Ids of the <see cref="IDictionaryItem" /></param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="IDictionaryItem" />
|
||||
/// </returns>
|
||||
IEnumerable<IDictionaryItem> GetDictionaryItemsByIds(params Guid[] ids) => Array.Empty<IDictionaryItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDictionaryItem" /> by its key
|
||||
/// </summary>
|
||||
@@ -58,6 +67,15 @@ public interface ILocalizationService : IService
|
||||
/// </returns>
|
||||
IDictionaryItem? GetDictionaryItemByKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IDictionaryItem" /> by their keys
|
||||
/// </summary>
|
||||
/// <param name="keys">Keys of the <see cref="IDictionaryItem" /></param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="IDictionaryItem" />
|
||||
/// </returns>
|
||||
IEnumerable<IDictionaryItem> GetDictionaryItemsByKeys(params string[] keys) => Array.Empty<IDictionaryItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of children for a <see cref="IDictionaryItem" />
|
||||
/// </summary>
|
||||
|
||||
@@ -170,6 +170,29 @@ internal class LocalizationService : RepositoryService, ILocalizationService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection <see cref="IDictionaryItem" /> by their <see cref="Guid" /> ids
|
||||
/// </summary>
|
||||
/// <param name="ids">Ids of the <see cref="IDictionaryItem" /></param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="IDictionaryItem" />
|
||||
/// </returns>
|
||||
public IEnumerable<IDictionaryItem> GetDictionaryItemsByIds(params Guid[] ids)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetMany(ids).ToArray();
|
||||
|
||||
// ensure the lazy Language callback is assigned
|
||||
foreach (IDictionaryItem item in items)
|
||||
{
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="IDictionaryItem" /> by its key
|
||||
/// </summary>
|
||||
@@ -189,6 +212,28 @@ internal class LocalizationService : RepositoryService, ILocalizationService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="IDictionaryItem" /> by their keys
|
||||
/// </summary>
|
||||
/// <param name="keys">Keys of the <see cref="IDictionaryItem" /></param>
|
||||
/// <returns>
|
||||
/// A collection of <see cref="IDictionaryItem" />
|
||||
/// </returns>
|
||||
public IEnumerable<IDictionaryItem> GetDictionaryItemsByKeys(params string[] keys)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
IEnumerable<IDictionaryItem> items = _dictionaryRepository.GetManyByKeys(keys).ToArray();
|
||||
|
||||
// ensure the lazy Language callback is assigned
|
||||
foreach (IDictionaryItem item in items)
|
||||
{
|
||||
EnsureDictionaryItemLanguageCallback(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of children for a <see cref="IDictionaryItem"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -33,6 +33,13 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
|
||||
return uniqueIdRepo.Get(uniqueId);
|
||||
}
|
||||
|
||||
public IEnumerable<IDictionaryItem> GetMany(params Guid[] uniqueIds)
|
||||
{
|
||||
var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches,
|
||||
_loggerFactory.CreateLogger<DictionaryByUniqueIdRepository>());
|
||||
return uniqueIdRepo.GetMany(uniqueIds);
|
||||
}
|
||||
|
||||
public IDictionaryItem? Get(string key)
|
||||
{
|
||||
var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches,
|
||||
@@ -40,6 +47,13 @@ internal class DictionaryRepository : EntityRepositoryBase<int, IDictionaryItem>
|
||||
return keyRepo.Get(key);
|
||||
}
|
||||
|
||||
public IEnumerable<IDictionaryItem> GetManyByKeys(string[] keys)
|
||||
{
|
||||
var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches,
|
||||
_loggerFactory.CreateLogger<DictionaryByKeyRepository>());
|
||||
return keyRepo.GetMany(keys);
|
||||
}
|
||||
|
||||
public Dictionary<string, Guid> GetDictionaryItemKeyMap()
|
||||
{
|
||||
var columns = new[] { "key", "id" }.Select(x => (object)SqlSyntax.GetQuotedColumnName(x)).ToArray();
|
||||
|
||||
@@ -680,7 +680,8 @@ internal class EntityRepository : RepositoryBase, IEntityRepositoryExtended
|
||||
|
||||
private EntitySlim BuildEntity(BaseDto dto)
|
||||
{
|
||||
if (dto.NodeObjectType == Constants.ObjectTypes.Document)
|
||||
if (dto.NodeObjectType == Constants.ObjectTypes.Document
|
||||
|| dto.NodeObjectType == Constants.ObjectTypes.DocumentBlueprint)
|
||||
{
|
||||
return BuildDocumentEntity(dto);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal abstract class SimpleGetRepository<TId, TEntity, TDto> : EntityReposito
|
||||
|
||||
protected override IEnumerable<TEntity> PerformGetAll(params TId[]? ids)
|
||||
{
|
||||
Sql<ISqlContext> sql = Sql().From<TEntity>();
|
||||
Sql<ISqlContext> sql = Sql().From<TDto>();
|
||||
|
||||
if (ids?.Any() ?? false)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.New.Cms.Web.Common.Routing;
|
||||
|
||||
public class VersionedApiBackOfficeRouteAttribute : BackOfficeRouteAttribute
|
||||
{
|
||||
public VersionedApiBackOfficeRouteAttribute(string template)
|
||||
: base($"api/v{{version:apiVersion}}/{template.TrimStart('/')}")
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user