Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Members and member types in the Management API (#15662)
* Members and member types in the Management API * Add validation endpoints for members * Include validation result in service response + add unit tests * Regenerate OpenApi.json * Regenerate OpenApi.json after merge * Don't throw an exception when trying to set valid variation levels for member types * Added missing ProducesResponseType * Remove TODO, as that works * Allow creation of member with explicit key * Do not feature "parent" for member creation + add missing response type * Do not feature a "Folder" in create member type (folders are not supported) * Added missing build methods * Fixed issue with mapping --------- Co-authored-by: Bjarke Berg <mail@bergmania.dk>
- Loading branch information
Showing
101 changed files
with
4,493 additions
and
263 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
src/Umbraco.Cms.Api.Management/Controllers/Member/ByKeyMemberController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using Asp.Versioning; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Api.Management.Factories; | ||
using Umbraco.Cms.Api.Management.ViewModels.Member; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Cms.Core.Services; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member; | ||
|
||
[ApiVersion("1.0")] | ||
public class ByKeyMemberController : MemberControllerBase | ||
{ | ||
private readonly IMemberEditingService _memberEditingService; | ||
private readonly IMemberPresentationFactory _memberPresentationFactory; | ||
|
||
public ByKeyMemberController(IMemberEditingService memberEditingService, IMemberPresentationFactory memberPresentationFactory) | ||
{ | ||
_memberEditingService = memberEditingService; | ||
_memberPresentationFactory = memberPresentationFactory; | ||
} | ||
|
||
[HttpGet("{id:guid}")] | ||
[MapToApiVersion("1.0")] | ||
[ProducesResponseType(typeof(MemberResponseModel), StatusCodes.Status200OK)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] | ||
public async Task<IActionResult> ByKey(Guid id) | ||
{ | ||
IMember? member = await _memberEditingService.GetAsync(id); | ||
if (member == null) | ||
{ | ||
return MemberNotFound(); | ||
} | ||
|
||
MemberResponseModel model = await _memberPresentationFactory.CreateResponseModelAsync(member); | ||
return Ok(model); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/Umbraco.Cms.Api.Management/Controllers/Member/CreateMemberController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
using Asp.Versioning; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Api.Management.Factories; | ||
using Umbraco.Cms.Api.Management.ViewModels.Member; | ||
using Umbraco.Cms.Core; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Cms.Core.Models.ContentEditing; | ||
using Umbraco.Cms.Core.Security; | ||
using Umbraco.Cms.Core.Services; | ||
using Umbraco.Cms.Core.Services.OperationStatus; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member; | ||
|
||
[ApiVersion("1.0")] | ||
public class CreateMemberController : MemberControllerBase | ||
{ | ||
private readonly IMemberEditingPresentationFactory _memberEditingPresentationFactory; | ||
private readonly IMemberEditingService _memberEditingService; | ||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; | ||
|
||
public CreateMemberController( | ||
IMemberEditingPresentationFactory memberEditingPresentationFactory, | ||
IMemberEditingService memberEditingService, | ||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor) | ||
{ | ||
_memberEditingPresentationFactory = memberEditingPresentationFactory; | ||
_memberEditingService = memberEditingService; | ||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor; | ||
} | ||
|
||
[HttpPost] | ||
[MapToApiVersion("1.0")] | ||
[ProducesResponseType(StatusCodes.Status201Created)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] | ||
public async Task<IActionResult> Create(CreateMemberRequestModel createRequestModel) | ||
{ | ||
MemberCreateModel model = _memberEditingPresentationFactory.MapCreateModel(createRequestModel); | ||
Attempt<MemberCreateResult, MemberEditingStatus> result = await _memberEditingService.CreateAsync(model, CurrentUser(_backOfficeSecurityAccessor)); | ||
|
||
return result.Success | ||
? CreatedAtId<ByKeyMemberController>(controller => nameof(controller.ByKey), result.Result.Content!.Key) | ||
: MemberEditingStatusResult(result.Status); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/Umbraco.Cms.Api.Management/Controllers/Member/DeleteMemberController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using Asp.Versioning; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Core; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Cms.Core.Security; | ||
using Umbraco.Cms.Core.Services; | ||
using Umbraco.Cms.Core.Services.OperationStatus; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member; | ||
|
||
[ApiVersion("1.0")] | ||
public class DeleteMemberController : MemberControllerBase | ||
{ | ||
private readonly IMemberEditingService _memberEditingService; | ||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; | ||
|
||
public DeleteMemberController(IMemberEditingService memberEditingService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) | ||
{ | ||
_memberEditingService = memberEditingService; | ||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor; | ||
} | ||
|
||
[HttpDelete("{id:guid}")] | ||
[MapToApiVersion("1.0")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] | ||
public async Task<IActionResult> Delete(Guid id) | ||
{ | ||
Attempt<IMember?, MemberEditingStatus> result = await _memberEditingService.DeleteAsync(id, CurrentUserKey(_backOfficeSecurityAccessor)); | ||
|
||
return result.Success | ||
? Ok() | ||
: MemberEditingStatusResult(result.Status); | ||
} | ||
} |
21 changes: 13 additions & 8 deletions
21
src/Umbraco.Cms.Api.Management/Controllers/Member/Item/ItemMemberItemController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,37 @@ | ||
using Asp.Versioning; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Api.Management.Factories; | ||
using Umbraco.Cms.Api.Management.ViewModels.Member.Item; | ||
using Umbraco.Cms.Core.Mapping; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Cms.Core.Models.Entities; | ||
using Umbraco.Cms.Core.Services; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member.Item; | ||
|
||
[ApiVersion("1.0")] | ||
public class ItemMemberItemController : MemberItemControllerBase | ||
{ | ||
private readonly IMemberService _memberService; | ||
private readonly IUmbracoMapper _mapper; | ||
private readonly IEntityService _entityService; | ||
private readonly IMemberPresentationFactory _memberPresentationFactory; | ||
|
||
public ItemMemberItemController(IMemberService memberService, IUmbracoMapper mapper) | ||
public ItemMemberItemController(IEntityService entityService, IMemberPresentationFactory memberPresentationFactory) | ||
{ | ||
_memberService = memberService; | ||
_mapper = mapper; | ||
_entityService = entityService; | ||
_memberPresentationFactory = memberPresentationFactory; | ||
} | ||
|
||
[HttpGet("item")] | ||
[MapToApiVersion("1.0")] | ||
[ProducesResponseType(typeof(IEnumerable<MemberItemResponseModel>), StatusCodes.Status200OK)] | ||
public async Task<IActionResult> Item([FromQuery(Name = "id")] HashSet<Guid> ids) | ||
{ | ||
IEnumerable<IMember> members = await _memberService.GetByKeysAsync(ids.ToArray()); | ||
List<MemberItemResponseModel> responseModels = _mapper.MapEnumerable<IMember, MemberItemResponseModel>(members); | ||
return Ok(responseModels); | ||
IEnumerable<IMemberEntitySlim> members = _entityService | ||
.GetAll(UmbracoObjectTypes.Member, ids.ToArray()) | ||
.OfType<IMemberEntitySlim>(); | ||
|
||
IEnumerable<MemberItemResponseModel> responseModels = members.Select(_memberPresentationFactory.CreateItemResponseModel); | ||
return await Task.FromResult(Ok(responseModels)); | ||
} | ||
} |
95 changes: 95 additions & 0 deletions
95
src/Umbraco.Cms.Api.Management/Controllers/Member/MemberControllerBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Api.Common.Builders; | ||
using Umbraco.Cms.Api.Management.Controllers.Content; | ||
using Umbraco.Cms.Api.Management.Routing; | ||
using Umbraco.Cms.Api.Management.ViewModels.Content; | ||
using Umbraco.Cms.Api.Management.ViewModels.Member; | ||
using Umbraco.Cms.Core; | ||
using Umbraco.Cms.Core.Models.ContentEditing; | ||
using Umbraco.Cms.Core.Services.OperationStatus; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member; | ||
|
||
[ApiController] | ||
[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Member)] | ||
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Member))] | ||
// FIXME: implement authorization | ||
// [Authorize(Policy = "New" + AuthorizationPolicies.SectionAccessMembers)] | ||
public class MemberControllerBase : ContentControllerBase | ||
{ | ||
protected IActionResult MemberNotFound() => NotFound(new ProblemDetailsBuilder() | ||
.WithTitle("The requested member could not be found") | ||
.Build()); | ||
|
||
protected IActionResult MemberEditingStatusResult(MemberEditingStatus status) | ||
=> status.MemberEditingOperationStatus is not MemberEditingOperationStatus.Success | ||
? MemberEditingOperationStatusResult(status.MemberEditingOperationStatus) | ||
: status.ContentEditingOperationStatus is not ContentEditingOperationStatus.Success | ||
? ContentEditingOperationStatusResult(status.ContentEditingOperationStatus) | ||
: throw new ArgumentException("Please handle success status explicitly in the controllers", nameof(status)); | ||
|
||
protected IActionResult MemberEditingOperationStatusResult(MemberEditingOperationStatus status) | ||
=> OperationStatusResult(status, problemDetailsBuilder => status switch | ||
{ | ||
MemberEditingOperationStatus.MemberNotFound => NotFound(problemDetailsBuilder | ||
.WithTitle("The requested member could not be found") | ||
.Build()), | ||
MemberEditingOperationStatus.MemberTypeNotFound => NotFound(problemDetailsBuilder | ||
.WithTitle("The requested member type could not be found") | ||
.Build()), | ||
MemberEditingOperationStatus.UnlockFailed => BadRequest(problemDetailsBuilder | ||
.WithTitle("Could not unlock the member") | ||
.WithDetail("Please refer to the logs for additional information.") | ||
.Build()), | ||
MemberEditingOperationStatus.DisableTwoFactorFailed => BadRequest(problemDetailsBuilder | ||
.WithTitle("Could not disable 2FA for the member") | ||
.WithDetail("Please refer to the logs for additional information.") | ||
.Build()), | ||
MemberEditingOperationStatus.RoleAssignmentFailed => BadRequest(problemDetailsBuilder | ||
.WithTitle("Could not update role assignments for the member") | ||
.WithDetail("Please refer to the logs for additional information.") | ||
.Build()), | ||
MemberEditingOperationStatus.PasswordChangeFailed => BadRequest(problemDetailsBuilder | ||
.WithTitle("Could not change the password") | ||
.WithDetail( | ||
"This is likely because the password did not meet the complexity requirements. The logs might hold additional information.") | ||
.Build()), | ||
MemberEditingOperationStatus.InvalidPassword => BadRequest(problemDetailsBuilder | ||
.WithTitle("Invalid password supplied") | ||
.WithDetail("The password did not meet the complexity requirements.") | ||
.Build()), | ||
MemberEditingOperationStatus.InvalidName => BadRequest(problemDetailsBuilder | ||
.WithTitle("Invalid name supplied") | ||
.Build()), | ||
MemberEditingOperationStatus.InvalidUsername => BadRequest(problemDetailsBuilder | ||
.WithTitle("Invalid username supplied") | ||
.Build()), | ||
MemberEditingOperationStatus.InvalidEmail => BadRequest(problemDetailsBuilder | ||
.WithTitle("Invalid email supplied") | ||
.Build()), | ||
MemberEditingOperationStatus.DuplicateUsername => BadRequest(problemDetailsBuilder | ||
.WithTitle("Duplicate username detected") | ||
.WithDetail("The supplied username is already in use by another member.") | ||
.Build()), | ||
MemberEditingOperationStatus.DuplicateEmail => BadRequest(problemDetailsBuilder | ||
.WithTitle("Duplicate email detected") | ||
.WithDetail("The supplied email is already in use by another member.") | ||
.Build()), | ||
MemberEditingOperationStatus.Unknown => StatusCode( | ||
StatusCodes.Status500InternalServerError, | ||
problemDetailsBuilder | ||
.WithTitle("Unknown error. Please see the log for more details.") | ||
.Build()), | ||
_ => StatusCode(StatusCodes.Status500InternalServerError, problemDetailsBuilder | ||
.WithTitle("Unknown member operation status.") | ||
.Build()) | ||
}); | ||
|
||
protected IActionResult MemberEditingOperationStatusResult<TContentModelBase>( | ||
ContentEditingOperationStatus status, | ||
TContentModelBase requestModel, | ||
ContentValidationResult validationResult) | ||
where TContentModelBase : ContentModelBase<MemberValueModel, MemberVariantRequestModel> | ||
=> ContentEditingOperationStatusResult<TContentModelBase, MemberValueModel, MemberVariantRequestModel>(status, requestModel, validationResult); | ||
} |
52 changes: 52 additions & 0 deletions
52
src/Umbraco.Cms.Api.Management/Controllers/Member/UpdateMemberController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using Asp.Versioning; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Umbraco.Cms.Api.Management.Factories; | ||
using Umbraco.Cms.Api.Management.ViewModels.Member; | ||
using Umbraco.Cms.Core; | ||
using Umbraco.Cms.Core.Models; | ||
using Umbraco.Cms.Core.Models.ContentEditing; | ||
using Umbraco.Cms.Core.Security; | ||
using Umbraco.Cms.Core.Services; | ||
using Umbraco.Cms.Core.Services.OperationStatus; | ||
|
||
namespace Umbraco.Cms.Api.Management.Controllers.Member; | ||
|
||
[ApiVersion("1.0")] | ||
public class UpdateMemberController : MemberControllerBase | ||
{ | ||
private readonly IMemberEditingService _memberEditingService; | ||
private readonly IMemberEditingPresentationFactory _memberEditingPresentationFactory; | ||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; | ||
|
||
public UpdateMemberController( | ||
IMemberEditingService memberEditingService, | ||
IMemberEditingPresentationFactory memberEditingPresentationFactory, | ||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor) | ||
{ | ||
_memberEditingService = memberEditingService; | ||
_memberEditingPresentationFactory = memberEditingPresentationFactory; | ||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor; | ||
} | ||
|
||
[HttpPut("{id:guid}")] | ||
[MapToApiVersion("1.0")] | ||
[ProducesResponseType(StatusCodes.Status200OK)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] | ||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] | ||
public async Task<IActionResult> Update(Guid id, UpdateMemberRequestModel updateRequestModel) | ||
{ | ||
IMember? member = await _memberEditingService.GetAsync(id); | ||
if (member == null) | ||
{ | ||
return MemberNotFound(); | ||
} | ||
|
||
MemberUpdateModel model = _memberEditingPresentationFactory.MapUpdateModel(updateRequestModel); | ||
Attempt<MemberUpdateResult, MemberEditingStatus> result = await _memberEditingService.UpdateAsync(member, model, CurrentUser(_backOfficeSecurityAccessor)); | ||
|
||
return result.Success | ||
? Ok() | ||
: MemberEditingStatusResult(result.Status); | ||
} | ||
} |
Oops, something went wrong.