* 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>
111 lines
4.2 KiB
C#
111 lines
4.2 KiB
C#
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
|
|
};
|
|
}
|