Member type container in management API (#16914)

* Member type container in management API

* Fix naming

* Update service

* Fix services

* Register IMemberTypeContainerService in DI container

Added a new service registration for `IMemberTypeContainerService`
in the `AddCoreServices` method of `UmbracoBuilder.cs`.

* Replace auditRepository with auditService in constructor

* Add MemberTypeContainer to UdiEntityType mapping

---------

Co-authored-by: georgebid <91198628+georgebid@users.noreply.github.com>
Co-authored-by: Sebastiaan Janssen <sebastiaan@umbraco.com>
This commit is contained in:
Bjarne Fyrstenborg
2025-10-07 15:31:35 +02:00
committed by GitHub
parent 0cf8279e65
commit f0cf4703fa
13 changed files with 223 additions and 23 deletions

View File

@@ -0,0 +1,25 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class ByKeyMemberTypeFolderController : MemberTypeFolderControllerBase
{
public ByKeyMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpGet("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FolderResponseModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ByKey(CancellationToken cancellationToken, Guid id) => await GetFolderAsync(id);
}

View File

@@ -0,0 +1,31 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class CreateMemberTypeFolderController : MemberTypeFolderControllerBase
{
public CreateMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Create(
CancellationToken cancellationToken,
CreateFolderRequestModel createFolderRequestModel)
=> await CreateFolderAsync<ByKeyMemberTypeFolderController>(
createFolderRequestModel,
controller => nameof(controller.ByKey)).ConfigureAwait(false);
}

View File

@@ -0,0 +1,25 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class DeleteMemberTypeFolderController : MemberTypeFolderControllerBase
{
public DeleteMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpDelete("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Delete(CancellationToken cancellationToken, Guid id) => await DeleteFolderAsync(id);
}

View File

@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.MemberType}/folder")]
[ApiExplorerSettings(GroupName = "Member Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public abstract class MemberTypeFolderControllerBase : FolderManagementControllerBase<IMemberType>
{
protected MemberTypeFolderControllerBase(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
}

View File

@@ -0,0 +1,30 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Folder;
[ApiVersion("1.0")]
public class UpdateMemberTypeFolderController : MemberTypeFolderControllerBase
{
public UpdateMemberTypeFolderController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeContainerService memberTypeContainerService)
: base(backOfficeSecurityAccessor, memberTypeContainerService)
{
}
[HttpPut("{id:guid}")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(
CancellationToken cancellationToken,
Guid id,
UpdateFolderResponseModel updateFolderResponseModel)
=> await UpdateFolderAsync(id, updateFolderResponseModel);
}

View File

@@ -19,6 +19,8 @@ public static partial class Constants
public static readonly Guid MediaTypeContainer = new(Strings.MediaTypeContainer); public static readonly Guid MediaTypeContainer = new(Strings.MediaTypeContainer);
public static readonly Guid MemberTypeContainer = new(Strings.MemberTypeContainer);
public static readonly Guid DocumentBlueprintContainer = new(Strings.DocumentBlueprintContainer); public static readonly Guid DocumentBlueprintContainer = new(Strings.DocumentBlueprintContainer);
public static readonly Guid DataType = new(Strings.DataType); public static readonly Guid DataType = new(Strings.DataType);
@@ -75,6 +77,8 @@ public static partial class Constants
public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4"; public const string MediaTypeContainer = "42AEF799-B288-4744-9B10-BE144B73CDC4";
public const string MemberTypeContainer = "59EF5767-7223-4ABC-B229-72821DC711B9";
public const string DocumentBlueprintContainer = "A7EFF71B-FA69-4552-93FC-038F7DEEE453"; public const string DocumentBlueprintContainer = "A7EFF71B-FA69-4552-93FC-038F7DEEE453";
public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A"; public const string ContentItem = "10E2B09F-C28B-476D-B77A-AA686435E44A";

View File

@@ -19,22 +19,27 @@ public static partial class Constants
// GUID entity types // GUID entity types
public const string AnyGuid = "any-guid"; // that one is for tests public const string AnyGuid = "any-guid"; // that one is for tests
public const string DataType = "data-type";
public const string DataTypeContainer = "data-type-container";
public const string DictionaryItem = "dictionary-item"; public const string DictionaryItem = "dictionary-item";
public const string Document = "document"; public const string Document = "document";
public const string DocumentBlueprint = "document-blueprint"; public const string DocumentBlueprint = "document-blueprint";
public const string DocumentBlueprintContainer = "document-blueprint-container"; public const string DocumentBlueprintContainer = "document-blueprint-container";
public const string DocumentType = "document-type"; public const string DocumentType = "document-type";
public const string DocumentTypeContainer = "document-type-container"; public const string DocumentTypeContainer = "document-type-container";
public const string MemberType = "member-type";
public const string MemberTypeContainer = "member-type-container";
public const string MemberGroup = "member-group";
public const string Member = "member";
public const string DataType = "data-type";
public const string DataTypeContainer = "data-type-container";
public const string Element = "element"; public const string Element = "element";
public const string Media = "media"; public const string Media = "media";
public const string MediaType = "media-type"; public const string MediaType = "media-type";
public const string MediaTypeContainer = "media-type-container"; public const string MediaTypeContainer = "media-type-container";
public const string Member = "member";
public const string MemberGroup = "member-group";
public const string MemberType = "member-type";
public const string Relation = "relation"; public const string Relation = "relation";
public const string RelationType = "relation-type"; public const string RelationType = "relation-type";
public const string Template = "template"; public const string Template = "template";
public const string User = "user"; public const string User = "user";

View File

@@ -329,6 +329,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<IMemberTypeService, MemberTypeService>(); Services.AddUnique<IMemberTypeService, MemberTypeService>();
Services.AddUnique<IMemberContentEditingService, MemberContentEditingService>(); Services.AddUnique<IMemberContentEditingService, MemberContentEditingService>();
Services.AddUnique<IMemberTypeEditingService, MemberTypeEditingService>(); Services.AddUnique<IMemberTypeEditingService, MemberTypeEditingService>();
Services.AddUnique<IMemberTypeContainerService, MemberTypeContainerService>();
Services.AddUnique<INotificationService, NotificationService>(); Services.AddUnique<INotificationService, NotificationService>();
Services.AddUnique<ITrackedReferencesService, TrackedReferencesService>(); Services.AddUnique<ITrackedReferencesService, TrackedReferencesService>();
Services.AddUnique<ITreeEntitySortingService, TreeEntitySortingService>(); Services.AddUnique<ITreeEntitySortingService, TreeEntitySortingService>();

View File

@@ -35,14 +35,6 @@ public enum UmbracoObjectTypes
[UmbracoUdiType(Constants.UdiEntityType.Media)] [UmbracoUdiType(Constants.UdiEntityType.Media)]
Media, Media,
/// <summary>
/// Member Type
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))]
[FriendlyName("Member Type")]
[UmbracoUdiType(Constants.UdiEntityType.MemberType)]
MemberType,
/// <summary> /// <summary>
/// Template /// Template
/// </summary> /// </summary>
@@ -52,12 +44,12 @@ public enum UmbracoObjectTypes
Template, Template,
/// <summary> /// <summary>
/// Member Group /// Document Type
/// </summary> /// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup, typeof(IMemberGroup))] [UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))]
[FriendlyName("Member Group")] [FriendlyName("Document Type")]
[UmbracoUdiType(Constants.UdiEntityType.MemberGroup)] [UmbracoUdiType(Constants.UdiEntityType.DocumentType)]
MemberGroup, DocumentType,
/// <summary> /// <summary>
/// "Media Type /// "Media Type
@@ -68,12 +60,20 @@ public enum UmbracoObjectTypes
MediaType, MediaType,
/// <summary> /// <summary>
/// Document Type /// Member Type
/// </summary> /// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.DocumentType, typeof(IContentType))] [UmbracoObjectType(Constants.ObjectTypes.Strings.MemberType, typeof(IMemberType))]
[FriendlyName("Document Type")] [FriendlyName("Member Type")]
[UmbracoUdiType(Constants.UdiEntityType.DocumentType)] [UmbracoUdiType(Constants.UdiEntityType.MemberType)]
DocumentType, MemberType,
/// <summary>
/// Member Group
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberGroup, typeof(IMemberGroup))]
[FriendlyName("Member Group")]
[UmbracoUdiType(Constants.UdiEntityType.MemberGroup)]
MemberGroup,
/// <summary> /// <summary>
/// Recycle Bin /// Recycle Bin
@@ -114,6 +114,14 @@ public enum UmbracoObjectTypes
[UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)] [UmbracoUdiType(Constants.UdiEntityType.MediaTypeContainer)]
MediaTypeContainer, MediaTypeContainer,
/// <summary>
/// Member type container
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.Strings.MemberTypeContainer)]
[FriendlyName("Member Type Container")]
[UmbracoUdiType(Constants.UdiEntityType.MemberTypeContainer)]
MemberTypeContainer,
/// <summary> /// <summary>
/// Media type container /// Media type container
/// </summary> /// </summary>

View File

@@ -0,0 +1,7 @@
using Umbraco.Cms.Core.Models;
namespace Umbraco.Cms.Core.Services;
public interface IMemberTypeContainerService : IEntityTypeContainerService<IMemberType>
{
}

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Cms.Core.Services.Locking;
internal static class MemberTypeLocks
{
// beware! order is important to avoid deadlocks
internal static int[] ReadLockIds { get; } = { Constants.Locks.MemberTypes };
internal static int[] WriteLockIds { get; } = { Constants.Locks.MemberTree, Constants.Locks.MemberTypes };
}

View File

@@ -0,0 +1,31 @@
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Locking;
namespace Umbraco.Cms.Core.Services;
internal sealed class MemberTypeContainerService : EntityTypeContainerService<IMemberType, IMemberTypeContainerRepository>, IMemberTypeContainerService
{
public MemberTypeContainerService(
ICoreScopeProvider provider,
ILoggerFactory loggerFactory,
IEventMessagesFactory eventMessagesFactory,
IMemberTypeContainerRepository entityContainerRepository,
IAuditService auditService,
IEntityRepository entityRepository,
IUserIdKeyResolver userIdKeyResolver)
: base(provider, loggerFactory, eventMessagesFactory, entityContainerRepository, auditService, entityRepository, userIdKeyResolver)
{
}
protected override Guid ContainedObjectType => Constants.ObjectTypes.MemberType;
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MemberTypeContainer;
protected override int[] ReadLockIds => MemberTypeLocks.ReadLockIds;
protected override int[] WriteLockIds => MemberTypeLocks.WriteLockIds;
}

View File

@@ -217,6 +217,7 @@ public sealed class UdiParser
{ Constants.UdiEntityType.Member, UdiType.GuidUdi }, { Constants.UdiEntityType.Member, UdiType.GuidUdi },
{ Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi }, { Constants.UdiEntityType.MemberGroup, UdiType.GuidUdi },
{ Constants.UdiEntityType.MemberType, UdiType.GuidUdi }, { Constants.UdiEntityType.MemberType, UdiType.GuidUdi },
{ Constants.UdiEntityType.MemberTypeContainer, UdiType.GuidUdi },
{ Constants.UdiEntityType.Relation, UdiType.GuidUdi }, { Constants.UdiEntityType.Relation, UdiType.GuidUdi },
{ Constants.UdiEntityType.RelationType, UdiType.GuidUdi }, { Constants.UdiEntityType.RelationType, UdiType.GuidUdi },
{ Constants.UdiEntityType.Template, UdiType.GuidUdi }, { Constants.UdiEntityType.Template, UdiType.GuidUdi },