Skip to content

Commit 6a7360a

Browse files
Member types: Implement containers (#20706)
* Add MemberType/MemberTypeContainer to supported EntityContainer object types * Implement MemberTypeContainerRepository * Update and add member type container API endpoints * Complete server and client-side implementation for member type container support. * Fix FE linting errors. * Export folder constants. * Applied suggestions from code review. * Updated management API authorization tests for member types. * Resolved breaking change on copy member type controller. * Allow content types to be moved to own folder without error. * Use flag providers for member type siblings endpoint. --------- Co-authored-by: Andy Butland <[email protected]>
1 parent f70f6d4 commit 6a7360a

File tree

107 files changed

+3085
-398
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+3085
-398
lines changed

src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ protected IActionResult MediaTypeImportOperationStatusResult(MediaTypeImportOper
3131
.WithDetail("The import failed due to not being able to convert the file into proper xml.")
3232
.Build()),
3333
MediaTypeImportOperationStatus.MediaTypeExists => BadRequest(problemDetailsBuilder
34-
.WithTitle("Failed to import because media type exits")
34+
.WithTitle("Failed to import because media type exists")
3535
.WithDetail("The import failed because the media type that was being imported already exits.")
3636
.Build()),
3737
MediaTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
@@ -42,6 +42,6 @@ protected IActionResult MediaTypeImportOperationStatusResult(MediaTypeImportOper
4242
.WithTitle("Invalid Id")
4343
.WithDetail("The import failed because the id of the media type you are trying to update did not match the id in the file.")
4444
.Build()),
45-
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown media type import operation status.")
45+
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown media type import operation status."),
4646
});
4747
}

src/Umbraco.Cms.Api.Management/Controllers/MemberType/CopyMemberTypeController.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
using Asp.Versioning;
1+
using Asp.Versioning;
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Http;
44
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
56
using Umbraco.Cms.Core;
67
using Umbraco.Cms.Core.Models;
78
using Umbraco.Cms.Core.Services;
@@ -24,9 +25,12 @@ public CopyMemberTypeController(IMemberTypeService memberTypeService)
2425
[ProducesResponseType(StatusCodes.Status201Created)]
2526
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
2627
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
27-
public async Task<IActionResult> Copy(CancellationToken cancellationToken, Guid id)
28+
public async Task<IActionResult> Copy(
29+
CancellationToken cancellationToken,
30+
Guid id,
31+
CopyMemberTypeRequestModel? copyMemberTypeRequestModel)
2832
{
29-
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.CopyAsync(id, containerKey: null);
33+
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.CopyAsync(id, copyMemberTypeRequestModel?.Target?.Id);
3034

3135
return result.Success
3236
? CreatedAtId<ByKeyMemberTypeController>(controller => nameof(controller.ByKey), result.Result!.Key)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.Factories;
6+
using Umbraco.Cms.Core.Models;
7+
using Umbraco.Cms.Core.Services;
8+
using Umbraco.Cms.Core.Services.OperationStatus;
9+
using Umbraco.Cms.Web.Common.Authorization;
10+
11+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
12+
13+
[ApiVersion("1.0")]
14+
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
15+
public class ExportMemberTypeController : MemberTypeControllerBase
16+
{
17+
private readonly IMemberTypeService _memberTypeService;
18+
private readonly IUdtFileContentFactory _fileContentFactory;
19+
20+
public ExportMemberTypeController(
21+
IMemberTypeService memberTypeService,
22+
IUdtFileContentFactory fileContentFactory)
23+
{
24+
_memberTypeService = memberTypeService;
25+
_fileContentFactory = fileContentFactory;
26+
}
27+
28+
[HttpGet("{id:guid}/export")]
29+
[MapToApiVersion("1.0")]
30+
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
31+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
32+
public IActionResult Export(
33+
CancellationToken cancellationToken,
34+
Guid id)
35+
{
36+
IMemberType? memberType = _memberTypeService.Get(id);
37+
if (memberType is null)
38+
{
39+
return OperationStatusResult(ContentTypeOperationStatus.NotFound);
40+
}
41+
42+
return _fileContentFactory.Create(memberType);
43+
}
44+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
6+
using Umbraco.Cms.Core;
7+
using Umbraco.Cms.Core.Models;
8+
using Umbraco.Cms.Core.Security;
9+
using Umbraco.Cms.Core.Services.ImportExport;
10+
using Umbraco.Cms.Core.Services.OperationStatus;
11+
using Umbraco.Cms.Web.Common.Authorization;
12+
13+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
14+
15+
[ApiVersion("1.0")]
16+
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
17+
public class ImportExistingMemberTypeController : MemberTypeControllerBase
18+
{
19+
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
20+
private readonly IMemberTypeImportService _memberTypeImportService;
21+
22+
public ImportExistingMemberTypeController(
23+
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
24+
IMemberTypeImportService memberTypeImportService)
25+
{
26+
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
27+
_memberTypeImportService = memberTypeImportService;
28+
}
29+
30+
[HttpPut("{id:guid}/import")]
31+
[MapToApiVersion("1.0")]
32+
[ProducesResponseType(StatusCodes.Status200OK)]
33+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
34+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
35+
public async Task<IActionResult> Import(
36+
CancellationToken cancellationToken,
37+
Guid id,
38+
ImportMemberTypeRequestModel model)
39+
{
40+
Attempt<IMemberType?, MemberTypeImportOperationStatus> importAttempt = await _memberTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
41+
42+
return importAttempt.Success is false
43+
? MemberTypeImportOperationStatusResult(importAttempt.Status)
44+
: Ok();
45+
}
46+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
6+
using Umbraco.Cms.Core;
7+
using Umbraco.Cms.Core.Models;
8+
using Umbraco.Cms.Core.Security;
9+
using Umbraco.Cms.Core.Services.ImportExport;
10+
using Umbraco.Cms.Core.Services.OperationStatus;
11+
using Umbraco.Cms.Web.Common.Authorization;
12+
13+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
14+
15+
[ApiVersion("1.0")]
16+
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
17+
public class ImportNewMemberTypeController : MemberTypeControllerBase
18+
{
19+
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
20+
private readonly IMemberTypeImportService _memberTypeImportService;
21+
22+
public ImportNewMemberTypeController(
23+
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
24+
IMemberTypeImportService memberTypeImportService)
25+
{
26+
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
27+
_memberTypeImportService = memberTypeImportService;
28+
}
29+
30+
[HttpPost("import")]
31+
[MapToApiVersion("1.0")]
32+
[ProducesResponseType(StatusCodes.Status201Created)]
33+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
34+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
35+
public async Task<IActionResult> Import(
36+
CancellationToken cancellationToken,
37+
ImportMemberTypeRequestModel model)
38+
{
39+
Attempt<IMemberType?, MemberTypeImportOperationStatus> importAttempt = await _memberTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
40+
41+
return importAttempt.Success is false
42+
? MemberTypeImportOperationStatusResult(importAttempt.Status)
43+
: CreatedAtId<ByKeyMemberTypeController>(controller => nameof(controller.ByKey), importAttempt.Result!.Key);
44+
}
45+
}

src/Umbraco.Cms.Api.Management/Controllers/MemberType/MemberTypeControllerBase.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.AspNetCore.Authorization;
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Http;
23
using Microsoft.AspNetCore.Mvc;
34
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
45
using Umbraco.Cms.Api.Management.Routing;
@@ -18,4 +19,29 @@ protected IActionResult OperationStatusResult(ContentTypeOperationStatus status)
1819

1920
protected IActionResult StructureOperationStatusResult(ContentTypeStructureOperationStatus status)
2021
=> DocumentTypeControllerBase.ContentTypeStructureOperationStatusResult(status, "member");
22+
23+
protected IActionResult MemberTypeImportOperationStatusResult(MemberTypeImportOperationStatus operationStatus) =>
24+
OperationStatusResult(operationStatus, problemDetailsBuilder => operationStatus switch
25+
{
26+
MemberTypeImportOperationStatus.TemporaryFileNotFound => NotFound(problemDetailsBuilder
27+
.WithTitle("Temporary file not found")
28+
.Build()),
29+
MemberTypeImportOperationStatus.TemporaryFileConversionFailure => BadRequest(problemDetailsBuilder
30+
.WithTitle("Failed to convert the specified file")
31+
.WithDetail("The import failed due to not being able to convert the file into proper xml.")
32+
.Build()),
33+
MemberTypeImportOperationStatus.MemberTypeExists => BadRequest(problemDetailsBuilder
34+
.WithTitle("Failed to import because member type exists")
35+
.WithDetail("The import failed because the member type that was being imported already exits.")
36+
.Build()),
37+
MemberTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
38+
.WithTitle("Type Mismatch")
39+
.WithDetail("The import failed because the file contained an entity that is not a member type.")
40+
.Build()),
41+
MemberTypeImportOperationStatus.IdMismatch => BadRequest(problemDetailsBuilder
42+
.WithTitle("Invalid Id")
43+
.WithDetail("The import failed because the id of the member type you are trying to update did not match the id in the file.")
44+
.Build()),
45+
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown member type import operation status."),
46+
});
2147
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Authorization;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Mvc;
5+
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
6+
using Umbraco.Cms.Core;
7+
using Umbraco.Cms.Core.Models;
8+
using Umbraco.Cms.Core.Services;
9+
using Umbraco.Cms.Core.Services.OperationStatus;
10+
using Umbraco.Cms.Web.Common.Authorization;
11+
12+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
13+
14+
[ApiVersion("1.0")]
15+
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
16+
public class MoveMemberTypeController : MemberTypeControllerBase
17+
{
18+
private readonly IMemberTypeService _memberTypeService;
19+
20+
public MoveMemberTypeController(IMemberTypeService memberTypeService)
21+
=> _memberTypeService = memberTypeService;
22+
23+
[HttpPut("{id:guid}/move")]
24+
[MapToApiVersion("1.0")]
25+
[ProducesResponseType(StatusCodes.Status200OK)]
26+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
27+
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
28+
public async Task<IActionResult> Move(
29+
CancellationToken cancellationToken,
30+
Guid id,
31+
MoveMemberTypeRequestModel moveMemberTypeRequestModel)
32+
{
33+
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.MoveAsync(id, moveMemberTypeRequestModel.Target?.Id);
34+
35+
return result.Success
36+
? Ok()
37+
: StructureOperationStatusResult(result.Status);
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Umbraco.Cms.Api.Management.Services.Flags;
5+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
6+
using Umbraco.Cms.Core.Services;
7+
8+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
9+
10+
[ApiVersion("1.0")]
11+
public class AncestorsMemberTypeTreeController : MemberTypeTreeControllerBase
12+
{
13+
public AncestorsMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
14+
: base(entityService, flagProviders, memberTypeService)
15+
{
16+
}
17+
18+
[HttpGet("ancestors")]
19+
[MapToApiVersion("1.0")]
20+
[ProducesResponseType(typeof(IEnumerable<MemberTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
21+
public async Task<ActionResult<IEnumerable<MemberTypeTreeItemResponseModel>>> Ancestors(CancellationToken cancellationToken, Guid descendantId)
22+
=> await GetAncestors(descendantId);
23+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Asp.Versioning;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Mvc;
4+
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
5+
using Umbraco.Cms.Api.Management.Services.Flags;
6+
using Umbraco.Cms.Api.Management.ViewModels.Tree;
7+
using Umbraco.Cms.Core.Services;
8+
9+
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
10+
11+
[ApiVersion("1.0")]
12+
public class ChildrenMemberTypeTreeController : MemberTypeTreeControllerBase
13+
{
14+
public ChildrenMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
15+
: base(entityService, flagProviders, memberTypeService)
16+
{ }
17+
18+
[HttpGet("children")]
19+
[MapToApiVersion("1.0")]
20+
[ProducesResponseType(typeof(PagedViewModel<MemberTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
21+
public async Task<ActionResult<PagedViewModel<MemberTypeTreeItemResponseModel>>> Children(
22+
CancellationToken cancellationToken,
23+
Guid parentId,
24+
int skip = 0,
25+
int take = 100,
26+
bool foldersOnly = false)
27+
{
28+
RenderFoldersOnly(foldersOnly);
29+
return await GetChildren(parentId, skip, take);
30+
}
31+
}

src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
1717
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.MemberType}")]
1818
[ApiExplorerSettings(GroupName = "Member Type")]
1919
[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)]
20-
public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<MemberTypeTreeItemResponseModel>
20+
public class MemberTypeTreeControllerBase : FolderTreeControllerBase<MemberTypeTreeItemResponseModel>
2121
{
2222
private readonly IMemberTypeService _memberTypeService;
2323

@@ -37,6 +37,8 @@ public MemberTypeTreeControllerBase(IEntityService entityService, FlagProviderCo
3737

3838
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType;
3939

40+
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MemberTypeContainer;
41+
4042
protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
4143
{
4244
var memberTypes = _memberTypeService

0 commit comments

Comments
 (0)