Skip to content

Commit 9baf040

Browse files
V16: Siblings endpoints (#19657)
* PoC implementation * Move to controller base * Implement solution that seems worse, but works better * Don't require parent key in repository method * Fix typos * Add siblings for data type, media type and media * Add endpoint for template * Add DocumentType and DocumentBlueprint controllers * Fix naming * Fix case if siblings are under root * Take item ordering into account not all entities are ordered by sort order * Add default implementation * Fix parentkey * Add tests * Format optimizations for split view * Add test covered requirement to description * Cover positive case and make test case output more readable * reduce allocations * Clarify test --------- Co-authored-by: Migaroez <[email protected]>
1 parent b5195ed commit 9baf040

File tree

15 files changed

+476
-2
lines changed

15 files changed

+476
-2
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
4+
using Umbraco.Cms.Core.Services;
5+
6+
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
7+
8+
public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase
9+
{
10+
public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
11+
: base(entityService, dataTypeService)
12+
{
13+
}
14+
15+
[HttpGet("siblings")]
16+
[ProducesResponseType(typeof(IEnumerable<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
17+
public Task<ActionResult<IEnumerable<DataTypeTreeItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after)
18+
=> GetSiblings(target, before, after);
19+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Umbraco.Cms.Api.Management.Factories;
5+
using Umbraco.Cms.Api.Management.Services.Entities;
6+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
7+
using Umbraco.Cms.Core.Cache;
8+
using Umbraco.Cms.Core.Security;
9+
using Umbraco.Cms.Core.Services;
10+
11+
namespace Umbraco.Cms.Api.Management.Controllers.Document.Tree;
12+
13+
[ApiVersion("1.0")]
14+
public class SiblingsDocumentTreeController : DocumentTreeControllerBase
15+
{
16+
public SiblingsDocumentTreeController(
17+
IEntityService entityService,
18+
IUserStartNodeEntitiesService userStartNodeEntitiesService,
19+
IDataTypeService dataTypeService,
20+
IPublicAccessService publicAccessService,
21+
AppCaches appCaches,
22+
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
23+
IDocumentPresentationFactory documentPresentationFactory)
24+
: base(
25+
entityService,
26+
userStartNodeEntitiesService,
27+
dataTypeService,
28+
publicAccessService,
29+
appCaches,
30+
backofficeSecurityAccessor,
31+
documentPresentationFactory)
32+
{
33+
}
34+
35+
[HttpGet("siblings")]
36+
[MapToApiVersion("1.0")]
37+
[ProducesResponseType(typeof(IEnumerable<DocumentTreeItemResponseModel>), StatusCodes.Status200OK)]
38+
public Task<ActionResult<IEnumerable<DocumentTreeItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after)
39+
=> GetSiblings(target, before, after);
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.Factories;
4+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
5+
using Umbraco.Cms.Core.Services;
6+
7+
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Tree;
8+
9+
public class SiblingsDocumentBlueprintTreeController : DocumentBlueprintTreeControllerBase
10+
{
11+
public SiblingsDocumentBlueprintTreeController(IEntityService entityService, IDocumentPresentationFactory documentPresentationFactory)
12+
: base(entityService, documentPresentationFactory)
13+
{
14+
}
15+
16+
[HttpGet("siblings")]
17+
[ProducesResponseType(typeof(IEnumerable<DocumentBlueprintTreeItemResponseModel>), StatusCodes.Status200OK)]
18+
public Task<ActionResult<IEnumerable<DocumentBlueprintTreeItemResponseModel>>> Siblings(
19+
CancellationToken cancellationToken,
20+
Guid target,
21+
int before,
22+
int after) =>
23+
GetSiblings(target, before, after);
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
4+
using Umbraco.Cms.Core.Services;
5+
6+
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Tree;
7+
8+
public class SiblingsDocumentTypeTreeController : DocumentTypeTreeControllerBase
9+
{
10+
public SiblingsDocumentTypeTreeController(IEntityService entityService, IContentTypeService contentTypeService)
11+
: base(entityService, contentTypeService)
12+
{
13+
}
14+
15+
[HttpGet("siblings")]
16+
[ProducesResponseType(typeof(IEnumerable<DocumentTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
17+
public Task<ActionResult<IEnumerable<DocumentTypeTreeItemResponseModel>>> Siblings(
18+
CancellationToken cancellationToken,
19+
Guid target,
20+
int before,
21+
int after) =>
22+
GetSiblings(target, before, after);
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.Factories;
4+
using Umbraco.Cms.Api.Management.Services.Entities;
5+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
6+
using Umbraco.Cms.Core.Cache;
7+
using Umbraco.Cms.Core.Security;
8+
using Umbraco.Cms.Core.Services;
9+
10+
namespace Umbraco.Cms.Api.Management.Controllers.Media.Tree;
11+
12+
public class SiblingsMediaTreeController : MediaTreeControllerBase
13+
{
14+
public SiblingsMediaTreeController(
15+
IEntityService entityService,
16+
IUserStartNodeEntitiesService userStartNodeEntitiesService,
17+
IDataTypeService dataTypeService,
18+
AppCaches appCaches,
19+
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
20+
IMediaPresentationFactory mediaPresentationFactory)
21+
: base(entityService, userStartNodeEntitiesService, dataTypeService, appCaches, backofficeSecurityAccessor, mediaPresentationFactory)
22+
{
23+
}
24+
25+
[HttpGet("siblings")]
26+
[ProducesResponseType(typeof(IEnumerable<MediaTreeItemResponseModel>), StatusCodes.Status200OK)]
27+
public Task<ActionResult<IEnumerable<MediaTreeItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after)
28+
=> GetSiblings(target, before, after);
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
4+
using Umbraco.Cms.Core.Services;
5+
6+
namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree;
7+
8+
public class SiblingsMediaTypeTreeController : MediaTypeTreeControllerBase
9+
{
10+
public SiblingsMediaTypeTreeController(IEntityService entityService, IMediaTypeService mediaTypeService)
11+
: base(entityService, mediaTypeService)
12+
{
13+
}
14+
15+
[HttpGet("siblings")]
16+
[ProducesResponseType(typeof(IEnumerable<MediaTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
17+
public Task<ActionResult<IEnumerable<MediaTypeTreeItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after)
18+
=> GetSiblings(target, before, after);
19+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.AspNetCore.Http;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
4+
using Umbraco.Cms.Core.Services;
5+
6+
namespace Umbraco.Cms.Api.Management.Controllers.Template.Tree;
7+
8+
public class SiblingsTemplateTreeController : TemplateTreeControllerBase
9+
{
10+
public SiblingsTemplateTreeController(IEntityService entityService)
11+
: base(entityService)
12+
{
13+
}
14+
15+
[HttpGet("siblings")]
16+
[ProducesResponseType(typeof(IEnumerable<NamedEntityTreeItemResponseModel>), StatusCodes.Status200OK)]
17+
public Task<ActionResult<IEnumerable<NamedEntityTreeItemResponseModel>>> Siblings(
18+
CancellationToken cancellationToken,
19+
Guid target,
20+
int before,
21+
int after) =>
22+
GetSiblings(target, before, after);
23+
}

src/Umbraco.Cms.Api.Management/Controllers/Tree/EntityTreeControllerBase.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using Microsoft.AspNetCore.Mvc;
22
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
33
using Umbraco.Cms.Api.Management.ViewModels;
44
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -44,6 +44,23 @@ protected Task<ActionResult<PagedViewModel<TItem>>> GetChildren(Guid parentId, i
4444
return Task.FromResult<ActionResult<PagedViewModel<TItem>>>(Ok(result));
4545
}
4646

47+
protected Task<ActionResult<IEnumerable<TItem>>> GetSiblings(Guid target, int before, int after)
48+
{
49+
IEntitySlim[] siblings = EntityService.GetSiblings(target, ItemObjectType, before, after, ItemOrdering).ToArray();
50+
if (siblings.Length == 0)
51+
{
52+
return Task.FromResult<ActionResult<IEnumerable<TItem>>>(NotFound());
53+
}
54+
55+
IEntitySlim? entity = siblings.FirstOrDefault();
56+
Guid? parentKey = entity?.ParentId > 0
57+
? EntityService.GetKey(entity.ParentId, ItemObjectType).Result
58+
: Constants.System.RootKey;
59+
60+
TItem[] treeItemsViewModels = MapTreeItemViewModels(parentKey, siblings);
61+
return Task.FromResult<ActionResult<IEnumerable<TItem>>>(Ok(treeItemsViewModels));
62+
}
63+
4764
protected virtual async Task<ActionResult<IEnumerable<TItem>>> GetAncestors(Guid descendantKey, bool includeSelf = true)
4865
{
4966
IEntitySlim[] ancestorEntities = await GetAncestorEntitiesAsync(descendantKey, includeSelf);

src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ public interface IEntityRepository : IRepository
1919

2020
IEnumerable<IEntitySlim> GetAll(Guid objectType, params Guid[] keys);
2121

22+
/// <summary>
23+
/// Gets sibling entities of a specified target entity, within a given range before and after the target, ordered as specified.
24+
/// </summary>
25+
/// <param name="objectType">The object type key of the entities.</param>
26+
/// <param name="targetKey">The key of the target entity whose siblings are to be retrieved.</param>
27+
/// <param name="before">The number of siblings to retrieve before the target entity.</param>
28+
/// <param name="after">The number of siblings to retrieve after the target entity.</param>
29+
/// <param name="ordering">The ordering to apply to the siblings.</param>
30+
/// <returns>Enumerable of sibling entities.</returns>
31+
IEnumerable<IEntitySlim> GetSiblings(Guid objectType, Guid targetKey, int before, int after, Ordering ordering) => [];
32+
2233
/// <summary>
2334
/// Gets entities for a query
2435
/// </summary>

src/Umbraco.Core/Services/EntityService.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,39 @@ public IEnumerable<IEntitySlim> GetChildren(Guid? key, UmbracoObjectTypes object
318318
return children;
319319
}
320320

321+
/// <inheritdoc />
322+
public IEnumerable<IEntitySlim> GetSiblings(
323+
Guid key,
324+
UmbracoObjectTypes objectType,
325+
int before,
326+
int after,
327+
Ordering? ordering = null)
328+
{
329+
if (before < 0)
330+
{
331+
throw new ArgumentOutOfRangeException(nameof(before), "The 'before' parameter must be greater than or equal to 0.");
332+
}
333+
334+
if (after < 0)
335+
{
336+
throw new ArgumentOutOfRangeException(nameof(after), "The 'after' parameter must be greater than or equal to 0.");
337+
}
338+
339+
ordering ??= new Ordering("sortOrder");
340+
341+
using ICoreScope scope = ScopeProvider.CreateCoreScope();
342+
343+
IEnumerable<IEntitySlim> siblings = _entityRepository.GetSiblings(
344+
objectType.GetGuid(),
345+
key,
346+
before,
347+
after,
348+
ordering);
349+
350+
scope.Complete();
351+
return siblings;
352+
}
353+
321354
/// <inheritdoc />
322355
public virtual IEnumerable<IEntitySlim> GetDescendants(int id)
323356
{

0 commit comments

Comments
 (0)