V14: Member group controller (#15669)

* Add models & mapping

* Add controller

* Add create async to service

* Add auth policy

* Implement delete

* Rename response model

* Implement updateAsync

* Refactor update to use own model

* Implement all async counterparts for IMemberService

* Add tests

* Implement update member group mapping

* Dont fail if updating the current user group

* Return not found if not found

* Add missing OperationResults to MemberGroupOperationStatusResult

* Add 404 to response type

* Update openapi

* Update OpenApi

* Update OpenApi.json
This commit is contained in:
Nikolaj Geisle
2024-02-29 09:16:27 +01:00
committed by GitHub
parent 3d5bb07ab1
commit 1e043cbcfb
17 changed files with 1170 additions and 48 deletions

View File

@@ -1,20 +1,74 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public interface IMemberGroupService : IService
{
[Obsolete("Please use the asynchronous counterpart. Scheduled for removal in v15.")]
IEnumerable<IMemberGroup> GetAll();
[Obsolete("Please use Guid instead of Int id. Scheduled for removal in v15.")]
IMemberGroup? GetById(int id);
[Obsolete("Please use the asynchronous counterpart. Scheduled for removal in v15.")]
IMemberGroup? GetById(Guid id);
[Obsolete("Please use the asynchronous counterpart. Scheduled for removal in v15.")]
IEnumerable<IMemberGroup> GetByIds(IEnumerable<int> ids);
IMemberGroup? GetByName(string? name);
[Obsolete("Please use the respective CreateAsync/UpdateAsync for you save operations. Scheduled for removal in v15.")]
void Save(IMemberGroup memberGroup);
[Obsolete("Please use the asynchronous counterpart. Scheduled for removal in v15.")]
void Delete(IMemberGroup memberGroup);
/// <summary>
/// Get a member group by name.
/// </summary>
/// <param name="name">Name of the member group to get.</param>
/// <returns>A <see cref="IMemberGroup" /> object.</returns>
Task<IMemberGroup?> GetByNameAsync(string name);
/// <summary>
/// Get a member group by key.
/// </summary>
/// <param name="key"><see cref="Guid" /> of the member group to get.</param>
/// <returns>A <see cref="IMemberGroup" /> object.</returns>
Task<IMemberGroup?> GetAsync(Guid key);
/// <summary>
/// Gets all member groups
/// </summary>
/// <returns>An enumerable list of <see cref="IMemberGroup" /> objects.</returns>
Task<IEnumerable<IMemberGroup>> GetAllAsync();
/// <summary>
/// Gets a list of member groups with the given ids.
/// </summary>
/// <param name="ids">An enumerable list of <see cref="int" /> ids, to get the member groups by.</param>
/// <returns>An enumerable list of <see cref="IMemberGroup" /> objects.</returns>
Task<IEnumerable<IMemberGroup>> GetByIdsAsync(IEnumerable<int> ids);
/// <summary>
/// Creates a new <see cref="IMemberGroup" /> object
/// </summary>
/// <param name="memberGroup"><see cref="IMemberGroup" /> to create</param>
/// <returns>An attempt with a status of whether the operation was successful or not, and the created object if it succeeded.</returns>
Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> CreateAsync(IMemberGroup memberGroup);
/// <summary>
/// Deletes a <see cref="IMemberGroup" /> by removing it and its usages from the db
/// </summary>
/// <param name="key">The key of the <see cref="IMemberGroup" /> to delete</param>
Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> DeleteAsync(Guid key);
/// <summary>
/// Updates <see cref="IMemberGroup" /> object
/// </summary>
/// <param name="memberGroup"><see cref="IMemberGroup" /> to create</param>
/// <returns>An attempt with a status of whether the operation was successful or not, and the object.</returns>
Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> UpdateAsync(IMemberGroup memberGroup);
}

View File

@@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
@@ -15,26 +16,9 @@ internal class MemberGroupService : RepositoryService, IMemberGroupService
: base(provider, loggerFactory, eventMessagesFactory) =>
_memberGroupRepository = memberGroupRepository;
public IEnumerable<IMemberGroup> GetAll()
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
return _memberGroupRepository.GetMany();
}
}
public IEnumerable<IMemberGroup> GetAll() => GetAllAsync().GetAwaiter().GetResult();
public IEnumerable<IMemberGroup> GetByIds(IEnumerable<int> ids)
{
if (ids == null || ids.Any() == false)
{
return new IMemberGroup[0];
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
return _memberGroupRepository.GetMany(ids.ToArray());
}
}
public IEnumerable<IMemberGroup> GetByIds(IEnumerable<int> ids) => GetByIdsAsync(ids).GetAwaiter().GetResult();
public IMemberGroup? GetById(int id)
{
@@ -44,21 +28,9 @@ internal class MemberGroupService : RepositoryService, IMemberGroupService
}
}
public IMemberGroup? GetById(Guid id)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
return _memberGroupRepository.Get(id);
}
}
public IMemberGroup? GetById(Guid id) => GetAsync(id).GetAwaiter().GetResult();
public IMemberGroup? GetByName(string? name)
{
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
{
return _memberGroupRepository.GetByName(name);
}
}
public IMemberGroup? GetByName(string? name) => name is null ? null : GetByNameAsync(name).GetAwaiter().GetResult();
public void Save(IMemberGroup memberGroup)
{
@@ -86,24 +58,139 @@ internal class MemberGroupService : RepositoryService, IMemberGroupService
}
}
public void Delete(IMemberGroup memberGroup)
public void Delete(IMemberGroup memberGroup) => DeleteAsync(memberGroup.Key).GetAwaiter().GetResult();
/// <inheritdoc/>
public Task<IMemberGroup?> GetByNameAsync(string name)
{
EventMessages evtMsgs = EventMessagesFactory.Get();
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return Task.FromResult(_memberGroupRepository.GetByName(name));
}
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
/// <inheritdoc/>
public Task<IMemberGroup?> GetAsync(Guid key)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return Task.FromResult(_memberGroupRepository.Get(key));
}
/// <inheritdoc/>
public Task<IEnumerable<IMemberGroup>> GetAllAsync()
{
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return Task.FromResult(_memberGroupRepository.GetMany());
}
public Task<IEnumerable<IMemberGroup>> GetByIdsAsync(IEnumerable<int> ids)
{
if (ids.Any() == false)
{
var deletingNotification = new MemberGroupDeletingNotification(memberGroup, evtMsgs);
if (scope.Notifications.PublishCancelable(deletingNotification))
{
scope.Complete();
return;
}
_memberGroupRepository.Delete(memberGroup);
scope.Complete();
scope.Notifications.Publish(
new MemberGroupDeletedNotification(memberGroup, evtMsgs).WithStateFrom(deletingNotification));
return Task.FromResult<IEnumerable<IMemberGroup>>(Array.Empty<IMemberGroup>());
}
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
return Task.FromResult(_memberGroupRepository.GetMany(ids.ToArray()));
}
/// <inheritdoc/>
public async Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> CreateAsync(IMemberGroup memberGroup)
{
if (string.IsNullOrWhiteSpace(memberGroup.Name))
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.CannotHaveEmptyName, null);
}
EventMessages eventMessages = EventMessagesFactory.Get();
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IMemberGroup? existingMemberGroup = await GetAsync(memberGroup.Key);
if (existingMemberGroup is not null)
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.DuplicateKey, null);
}
if (await NameAlreadyExistsAsync(memberGroup))
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.DuplicateName, null);
}
var savingNotification = new MemberGroupSavingNotification(memberGroup, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(savingNotification))
{
scope.Complete();
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.CancelledByNotification, null);
}
_memberGroupRepository.Save(memberGroup);
scope.Complete();
scope.Notifications.Publish(new MemberGroupSavedNotification(memberGroup, eventMessages).WithStateFrom(savingNotification));
return Attempt.SucceedWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.Success, memberGroup);
}
/// <inheritdoc/>
public async Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> DeleteAsync(Guid key)
{
EventMessages eventMessages = EventMessagesFactory.Get();
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IMemberGroup? memberGroup = _memberGroupRepository.Get(key);
if (memberGroup is null)
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.NotFound, null);
}
var deletingNotification = new MemberGroupDeletingNotification(memberGroup, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(deletingNotification))
{
scope.Complete();
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.CancelledByNotification, null);
}
_memberGroupRepository.Delete(memberGroup);
scope.Complete();
scope.Notifications.Publish(new MemberGroupDeletedNotification(memberGroup, eventMessages).WithStateFrom(deletingNotification));
return Attempt.SucceedWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.Success, memberGroup);
}
public async Task<Attempt<IMemberGroup?, MemberGroupOperationStatus>> UpdateAsync(IMemberGroup memberGroup)
{
if (string.IsNullOrWhiteSpace(memberGroup.Name))
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.CannotHaveEmptyName, null);
}
EventMessages eventMessages = EventMessagesFactory.Get();
using ICoreScope scope = ScopeProvider.CreateCoreScope();
IMemberGroup? existingMemberGroup = await GetByNameAsync(memberGroup.Name!);
if (existingMemberGroup is not null && existingMemberGroup.Key != memberGroup.Key)
{
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.DuplicateName, null);
}
var savingNotification = new MemberGroupSavingNotification(memberGroup, eventMessages);
if (await scope.Notifications.PublishCancelableAsync(savingNotification))
{
scope.Complete();
return Attempt.FailWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.CancelledByNotification, null);
}
_memberGroupRepository.Save(memberGroup);
scope.Complete();
scope.Notifications.Publish(new MemberGroupSavedNotification(memberGroup, eventMessages).WithStateFrom(savingNotification));
return Attempt.SucceedWithStatus<IMemberGroup?, MemberGroupOperationStatus>(MemberGroupOperationStatus.Success, memberGroup);
}
private async Task<bool> NameAlreadyExistsAsync(IMemberGroup memberGroup)
{
IMemberGroup? existingMemberGroup = await GetByNameAsync(memberGroup.Name!);
return existingMemberGroup is not null;
}
}

View File

@@ -0,0 +1,11 @@
namespace Umbraco.Cms.Core.Services.OperationStatus;
public enum MemberGroupOperationStatus
{
Success,
NotFound,
CannotHaveEmptyName,
CancelledByNotification,
DuplicateName,
DuplicateKey,
}