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 <abutland73@gmail.com>
This commit is contained in:
Ronald Barendse
2025-11-20 11:28:03 +01:00
committed by GitHub
parent f70f6d4aba
commit 6a7360aded
107 changed files with 2897 additions and 210 deletions

View File

@@ -31,7 +31,7 @@ public abstract class MediaTypeControllerBase : ManagementApiControllerBase
.WithDetail("The import failed due to not being able to convert the file into proper xml.")
.Build()),
MediaTypeImportOperationStatus.MediaTypeExists => BadRequest(problemDetailsBuilder
.WithTitle("Failed to import because media type exits")
.WithTitle("Failed to import because media type exists")
.WithDetail("The import failed because the media type that was being imported already exits.")
.Build()),
MediaTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
@@ -42,6 +42,6 @@ public abstract class MediaTypeControllerBase : ManagementApiControllerBase
.WithTitle("Invalid Id")
.WithDetail("The import failed because the id of the media type you are trying to update did not match the id in the file.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown media type import operation status.")
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown media type import operation status."),
});
}

View File

@@ -1,7 +1,8 @@
using Asp.Versioning;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
@@ -24,9 +25,12 @@ public class CopyMemberTypeController : MemberTypeControllerBase
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Copy(CancellationToken cancellationToken, Guid id)
public async Task<IActionResult> Copy(
CancellationToken cancellationToken,
Guid id,
CopyMemberTypeRequestModel? copyMemberTypeRequestModel)
{
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.CopyAsync(id, containerKey: null);
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.CopyAsync(id, copyMemberTypeRequestModel?.Target?.Id);
return result.Success
? CreatedAtId<ByKeyMemberTypeController>(controller => nameof(controller.ByKey), result.Result!.Key)

View File

@@ -0,0 +1,44 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Factories;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public class ExportMemberTypeController : MemberTypeControllerBase
{
private readonly IMemberTypeService _memberTypeService;
private readonly IUdtFileContentFactory _fileContentFactory;
public ExportMemberTypeController(
IMemberTypeService memberTypeService,
IUdtFileContentFactory fileContentFactory)
{
_memberTypeService = memberTypeService;
_fileContentFactory = fileContentFactory;
}
[HttpGet("{id:guid}/export")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public IActionResult Export(
CancellationToken cancellationToken,
Guid id)
{
IMemberType? memberType = _memberTypeService.Get(id);
if (memberType is null)
{
return OperationStatusResult(ContentTypeOperationStatus.NotFound);
}
return _fileContentFactory.Create(memberType);
}
}

View File

@@ -0,0 +1,46 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public class ImportExistingMemberTypeController : MemberTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IMemberTypeImportService _memberTypeImportService;
public ImportExistingMemberTypeController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeImportService memberTypeImportService)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_memberTypeImportService = memberTypeImportService;
}
[HttpPut("{id:guid}/import")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Import(
CancellationToken cancellationToken,
Guid id,
ImportMemberTypeRequestModel model)
{
Attempt<IMemberType?, MemberTypeImportOperationStatus> importAttempt = await _memberTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
return importAttempt.Success is false
? MemberTypeImportOperationStatusResult(importAttempt.Status)
: Ok();
}
}

View File

@@ -0,0 +1,45 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services.ImportExport;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public class ImportNewMemberTypeController : MemberTypeControllerBase
{
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
private readonly IMemberTypeImportService _memberTypeImportService;
public ImportNewMemberTypeController(
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
IMemberTypeImportService memberTypeImportService)
{
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
_memberTypeImportService = memberTypeImportService;
}
[HttpPost("import")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Import(
CancellationToken cancellationToken,
ImportMemberTypeRequestModel model)
{
Attempt<IMemberType?, MemberTypeImportOperationStatus> importAttempt = await _memberTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
return importAttempt.Success is false
? MemberTypeImportOperationStatusResult(importAttempt.Status)
: CreatedAtId<ByKeyMemberTypeController>(controller => nameof(controller.ByKey), importAttempt.Result!.Key);
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
using Umbraco.Cms.Api.Management.Routing;
@@ -18,4 +19,29 @@ public abstract class MemberTypeControllerBase : ManagementApiControllerBase
protected IActionResult StructureOperationStatusResult(ContentTypeStructureOperationStatus status)
=> DocumentTypeControllerBase.ContentTypeStructureOperationStatusResult(status, "member");
protected IActionResult MemberTypeImportOperationStatusResult(MemberTypeImportOperationStatus operationStatus) =>
OperationStatusResult(operationStatus, problemDetailsBuilder => operationStatus switch
{
MemberTypeImportOperationStatus.TemporaryFileNotFound => NotFound(problemDetailsBuilder
.WithTitle("Temporary file not found")
.Build()),
MemberTypeImportOperationStatus.TemporaryFileConversionFailure => BadRequest(problemDetailsBuilder
.WithTitle("Failed to convert the specified file")
.WithDetail("The import failed due to not being able to convert the file into proper xml.")
.Build()),
MemberTypeImportOperationStatus.MemberTypeExists => BadRequest(problemDetailsBuilder
.WithTitle("Failed to import because member type exists")
.WithDetail("The import failed because the member type that was being imported already exits.")
.Build()),
MemberTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Type Mismatch")
.WithDetail("The import failed because the file contained an entity that is not a member type.")
.Build()),
MemberTypeImportOperationStatus.IdMismatch => BadRequest(problemDetailsBuilder
.WithTitle("Invalid Id")
.WithDetail("The import failed because the id of the member type you are trying to update did not match the id in the file.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown member type import operation status."),
});
}

View File

@@ -0,0 +1,39 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType;
[ApiVersion("1.0")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)]
public class MoveMemberTypeController : MemberTypeControllerBase
{
private readonly IMemberTypeService _memberTypeService;
public MoveMemberTypeController(IMemberTypeService memberTypeService)
=> _memberTypeService = memberTypeService;
[HttpPut("{id:guid}/move")]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> Move(
CancellationToken cancellationToken,
Guid id,
MoveMemberTypeRequestModel moveMemberTypeRequestModel)
{
Attempt<IMemberType?, ContentTypeStructureOperationStatus> result = await _memberTypeService.MoveAsync(id, moveMemberTypeRequestModel.Target?.Id);
return result.Success
? Ok()
: StructureOperationStatusResult(result.Status);
}
}

View File

@@ -0,0 +1,23 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
[ApiVersion("1.0")]
public class AncestorsMemberTypeTreeController : MemberTypeTreeControllerBase
{
public AncestorsMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
: base(entityService, flagProviders, memberTypeService)
{
}
[HttpGet("ancestors")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(IEnumerable<MemberTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<MemberTypeTreeItemResponseModel>>> Ancestors(CancellationToken cancellationToken, Guid descendantId)
=> await GetAncestors(descendantId);
}

View File

@@ -0,0 +1,31 @@
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
[ApiVersion("1.0")]
public class ChildrenMemberTypeTreeController : MemberTypeTreeControllerBase
{
public ChildrenMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
: base(entityService, flagProviders, memberTypeService)
{ }
[HttpGet("children")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(PagedViewModel<MemberTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
public async Task<ActionResult<PagedViewModel<MemberTypeTreeItemResponseModel>>> Children(
CancellationToken cancellationToken,
Guid parentId,
int skip = 0,
int take = 100,
bool foldersOnly = false)
{
RenderFoldersOnly(foldersOnly);
return await GetChildren(parentId, skip, take);
}
}

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.MemberType}")]
[ApiExplorerSettings(GroupName = "Member Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)]
public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<MemberTypeTreeItemResponseModel>
public class MemberTypeTreeControllerBase : FolderTreeControllerBase<MemberTypeTreeItemResponseModel>
{
private readonly IMemberTypeService _memberTypeService;
@@ -37,6 +37,8 @@ public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<Member
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType;
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MemberTypeContainer;
protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
{
var memberTypes = _memberTypeService

View File

@@ -32,5 +32,8 @@ public class RootMemberTypeTreeController : MemberTypeTreeControllerBase
int skip = 0,
int take = 100,
bool foldersOnly = false)
=> await GetRoot(skip, take);
{
RenderFoldersOnly(foldersOnly);
return await GetRoot(skip, take);
}
}

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -9,10 +10,14 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase
{
public SiblingMemberTypeTreeController(
IEntityService entityService,
FlagProviderCollection flagProviders,
IMemberTypeService memberTypeService)
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
public SiblingMemberTypeTreeController(IEntityService entityService, IMemberTypeService memberTypeService)
: base(entityService, memberTypeService)
{
}
[ActivatorUtilitiesConstructor]
public SiblingMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
: base(entityService, flagProviders, memberTypeService)
{
}
@@ -25,5 +30,8 @@ public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase
int before,
int after,
bool foldersOnly = false)
=> await GetSiblings(target, before, after);
{
RenderFoldersOnly(foldersOnly);
return await GetSiblings(target, before, after);
}
}

View File

@@ -8,4 +8,6 @@ public interface IUdtFileContentFactory
FileContentResult Create(IContentType contentType);
FileContentResult Create(IMediaType mediaType);
FileContentResult Create(IMemberType mediaType) => throw new NotImplementedException();
}

View File

@@ -1,4 +1,4 @@
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Services;
@@ -24,6 +24,7 @@ internal sealed class MemberTypeEditingPresentationFactory : ContentTypeEditingP
createModel.Key = requestModel.Id;
createModel.Compositions = MapCompositions(requestModel.Compositions);
createModel.ContainerKey = requestModel.Parent?.Id;
MapPropertyTypeSensitivityAndVisibility(createModel.Properties, requestModel.Properties);

View File

@@ -27,6 +27,12 @@ public class UdtFileContentFactory : IUdtFileContentFactory
return XmlTofile(mediaType, xml);
}
public FileContentResult Create(IMemberType memberType)
{
XElement xml = _entityXmlSerializer.Serialize(memberType);
return XmlTofile(memberType, xml);
}
private static FileContentResult XmlTofile(IContentTypeBase contentTypeBase, XElement xml) =>
new(Encoding.UTF8.GetBytes(xml.ToDataString()), MediaTypeNames.Application.Octet)
{

View File

@@ -11898,6 +11898,95 @@
]
}
},
"/umbraco/management/api/v1/help": {
"get": {
"tags": [
"Help"
],
"operationId": "GetHelp",
"parameters": [
{
"name": "section",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "tree",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
},
{
"name": "baseUrl",
"in": "query",
"schema": {
"type": "string",
"default": "https://our.umbraco.com"
}
}
],
"responses": {
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedHelpPageResponseModel"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
}
},
"deprecated": true,
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/imaging/resize/urls": {
"get": {
"tags": [
@@ -19996,6 +20085,37 @@
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CopyMemberTypeRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CopyMemberTypeRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/CopyMemberTypeRequestModel"
}
]
}
}
}
},
"responses": {
"201": {
"description": "Created",
@@ -20105,6 +20225,351 @@
]
}
},
"/umbraco/management/api/v1/member-type/{id}/export": {
"get": {
"tags": [
"Member Type"
],
"operationId": "GetMemberTypeByIdExport",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"type": "string",
"format": "binary"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/member-type/{id}/import": {
"put": {
"tags": [
"Member Type"
],
"operationId": "PutMemberTypeByIdImport",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
}
}
},
"responses": {
"200": {
"description": "OK",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/member-type/{id}/move": {
"put": {
"tags": [
"Member Type"
],
"operationId": "PutMemberTypeByIdMove",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/MoveMemberTypeRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/MoveMemberTypeRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/MoveMemberTypeRequestModel"
}
]
}
}
}
},
"responses": {
"200": {
"description": "OK",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/member-type/available-compositions": {
"post": {
"tags": [
@@ -20688,6 +21153,271 @@
]
}
},
"/umbraco/management/api/v1/member-type/import": {
"post": {
"tags": [
"Member Type"
],
"operationId": "PostMemberTypeImport",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
},
"text/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
},
"application/*+json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ImportMemberTypeRequestModel"
}
]
}
}
}
},
"responses": {
"201": {
"description": "Created",
"headers": {
"Umb-Generated-Resource": {
"description": "Identifier of the newly created resource",
"schema": {
"type": "string",
"description": "Identifier of the newly created resource"
}
},
"Location": {
"description": "Location of the newly created resource",
"schema": {
"type": "string",
"description": "Location of the newly created resource",
"format": "uri"
}
},
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
},
"404": {
"description": "Not Found",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"400": {
"description": "Bad Request",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
},
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ProblemDetails"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource",
"headers": {
"Umb-Notifications": {
"description": "The list of notifications produced during the request.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NotificationHeaderModel"
},
"nullable": true
}
}
}
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/tree/member-type/ancestors": {
"get": {
"tags": [
"Member Type"
],
"operationId": "GetTreeMemberTypeAncestors",
"parameters": [
{
"name": "descendantId",
"in": "query",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/MemberTypeTreeItemResponseModel"
}
]
}
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/tree/member-type/children": {
"get": {
"tags": [
"Member Type"
],
"operationId": "GetTreeMemberTypeChildren",
"parameters": [
{
"name": "parentId",
"in": "query",
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "skip",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 0
}
},
{
"name": "take",
"in": "query",
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
},
{
"name": "foldersOnly",
"in": "query",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/PagedMemberTypeTreeItemResponseModel"
}
]
}
}
}
},
"401": {
"description": "The resource is protected and requires an authentication token"
},
"403": {
"description": "The authenticated user does not have access to this resource"
}
},
"security": [
{
"Backoffice-User": [ ]
}
]
}
},
"/umbraco/management/api/v1/tree/member-type/root": {
"get": {
"tags": [
@@ -20712,6 +21442,14 @@
"format": "int32",
"default": 100
}
},
{
"name": "foldersOnly",
"in": "query",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
@@ -20773,6 +21511,14 @@
"type": "integer",
"format": "int32"
}
},
{
"name": "foldersOnly",
"in": "query",
"schema": {
"type": "boolean",
"default": false
}
}
],
"responses": {
@@ -36266,6 +37012,20 @@
},
"additionalProperties": false
},
"CopyMemberTypeRequestModel": {
"type": "object",
"properties": {
"target": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
}
},
"additionalProperties": false
},
"CreateDataTypeRequestModel": {
"required": [
"editorAlias",
@@ -37361,6 +38121,14 @@
"format": "uuid",
"nullable": true
},
"parent": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
},
"compositions": {
"type": "array",
"items": {
@@ -40574,7 +41342,7 @@
},
"actionParameters": {
"type": "object",
"additionalProperties": {},
"additionalProperties": { },
"nullable": true
}
},
@@ -40824,6 +41592,22 @@
},
"additionalProperties": false
},
"ImportMemberTypeRequestModel": {
"required": [
"file"
],
"type": "object",
"properties": {
"file": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"additionalProperties": false
},
"IndexResponseModel": {
"required": [
"canRebuild",
@@ -41199,7 +41983,7 @@
},
"extensions": {
"type": "array",
"items": {}
"items": { }
}
},
"additionalProperties": false
@@ -42852,6 +43636,7 @@
"hasChildren",
"icon",
"id",
"isFolder",
"name"
],
"type": "object",
@@ -42884,6 +43669,9 @@
"name": {
"type": "string"
},
"isFolder": {
"type": "boolean"
},
"icon": {
"type": "string"
}
@@ -43129,6 +43917,20 @@
},
"additionalProperties": false
},
"MoveMemberTypeRequestModel": {
"type": "object",
"properties": {
"target": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
}
},
"additionalProperties": false
},
"NamedEntityTreeItemResponseModel": {
"required": [
"flags",
@@ -45071,7 +45873,7 @@
"nullable": true
}
},
"additionalProperties": {}
"additionalProperties": { }
},
"ProblemDetailsBuilderModel": {
"type": "object",
@@ -48718,8 +49520,7 @@
"type": "string"
},
"subscribeToNewsletter": {
"type": "boolean",
"readOnly": true
"type": "boolean"
}
},
"additionalProperties": false

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.MemberType;
public class CopyMemberTypeRequestModel
{
public ReferenceByIdModel? Target { get; set; }
}

View File

@@ -1,9 +1,9 @@
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
using Umbraco.Cms.Api.Management.ViewModels.ContentType;
namespace Umbraco.Cms.Api.Management.ViewModels.MemberType;
public class CreateMemberTypeRequestModel
: CreateContentTypeRequestModelBase<CreateMemberTypePropertyTypeRequestModel, CreateMemberTypePropertyTypeContainerRequestModel>
: CreateContentTypeWithParentRequestModelBase<CreateMemberTypePropertyTypeRequestModel, CreateMemberTypePropertyTypeContainerRequestModel>
{
public IEnumerable<MemberTypeComposition> Compositions { get; set; } = Enumerable.Empty<MemberTypeComposition>();
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.MemberType;
public class ImportMemberTypeRequestModel
{
public required ReferenceByIdModel File { get; set; }
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.MemberType;
public class MoveMemberTypeRequestModel
{
public ReferenceByIdModel? Target { get; set; }
}

View File

@@ -1,6 +1,6 @@
namespace Umbraco.Cms.Api.Management.ViewModels.Tree;
namespace Umbraco.Cms.Api.Management.ViewModels.Tree;
public class MemberTypeTreeItemResponseModel : NamedEntityTreeItemResponseModel
public class MemberTypeTreeItemResponseModel : FolderTreeItemResponseModel
{
public string Icon { get; set; } = string.Empty;
}

View File

@@ -430,6 +430,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>();
Services.AddUnique<IContentTypeImportService, ContentTypeImportService>();
Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>();
Services.AddUnique<IMemberTypeImportService, MemberTypeImportService>();
// add validation services
Services.AddUnique<IElementSwitchValidator, ElementSwitchValidator>();

View File

@@ -1,6 +1,8 @@
namespace Umbraco.Cms.Core.Models.ContentTypeEditing;
namespace Umbraco.Cms.Core.Models.ContentTypeEditing;
public class MemberTypeCreateModel : MemberTypeModelBase
{
public Guid? Key { get; set; }
public Guid? ContainerKey { get; set; }
}

View File

@@ -1,4 +1,4 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Services.OperationStatus;

View File

@@ -29,7 +29,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
public async Task<Attempt<IMemberType?, ContentTypeOperationStatus>> CreateAsync(MemberTypeCreateModel model, Guid userKey)
{
Attempt<IMemberType?, ContentTypeOperationStatus> result = await ValidateAndMapForCreationAsync(model, model.Key, containerKey: null);
Attempt<IMemberType?, ContentTypeOperationStatus> result = await ValidateAndMapForCreationAsync(model, model.Key, model.ContainerKey);
if (result.Success is false)
{
return result;
@@ -80,7 +80,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.MemberType;
protected override UmbracoObjectTypes ContainerObjectType => throw new NotSupportedException("Member type tree does not support containers");
protected override UmbracoObjectTypes ContainerObjectType => UmbracoObjectTypes.MemberTypeContainer;
protected override ISet<string> GetReservedFieldNames() => _reservedFieldNamesService.GetMemberReservedFieldNames();

View File

@@ -1053,9 +1053,9 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
public Attempt<OperationResult<MoveOperationStatusType>?> Move(TItem moving, int containerId)
{
EventMessages eventMessages = EventMessagesFactory.Get();
if(moving.ParentId == containerId)
if (moving.ParentId == containerId)
{
return OperationResult.Attempt.Fail(MoveOperationStatusType.FailedNotAllowedByPath, eventMessages);
return OperationResult.Attempt.Succeed(MoveOperationStatusType.Success, eventMessages);
}
var moveInfo = new List<MoveEventInfo<TItem>>();

View File

@@ -374,7 +374,11 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
return xml;
}
public XElement Serialize(IMediaType mediaType)
public XElement Serialize(IMediaType mediaType) => SerializeMediaOrMemberType(mediaType, IEntityXmlSerializer.MediaTypeElementName);
public XElement Serialize(IMemberType memberType) => SerializeMediaOrMemberType(memberType, IEntityXmlSerializer.MemberTypeElementName);
private XElement SerializeMediaOrMemberType(IContentTypeComposition mediaType, string elementName)
{
var info = new XElement(
"Info",
@@ -410,7 +414,7 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
var xml = new XElement(
IEntityXmlSerializer.MediaTypeElementName,
elementName,
info,
structure,
genericProperties,

View File

@@ -10,6 +10,7 @@ public interface IEntityXmlSerializer
{
internal const string DocumentTypeElementName = "DocumentType";
internal const string MediaTypeElementName = "MediaType";
internal const string MemberTypeElementName = "MemberType";
/// <summary>
/// Exports an IContent item as an XElement.
@@ -80,5 +81,7 @@ public interface IEntityXmlSerializer
XElement Serialize(IMediaType mediaType);
XElement Serialize(IMemberType memberType) => throw new NotImplementedException();
XElement Serialize(IContentType contentType);
}

View File

@@ -10,13 +10,21 @@ public interface IPackageDataInstallation
InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId);
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// Imports and saves package xml as <see cref="IMediaType"/>.
/// </summary>
/// <param name="docTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <returns>An enumerable list of generated ContentTypes</returns>
/// <returns>An enumerable list of generated <see cref="IMediaType"/>s.</returns>
IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId);
/// <summary>
/// Imports and saves package xml as <see cref="IMemberType"/>.
/// </summary>
/// <param name="docTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <returns>An enumerable list of generated <see cref="IMemberType"/>s.</returns>
IReadOnlyList<IMemberType> ImportMemberTypes(IEnumerable<XElement> docTypeElements, int userId) => throw new NotImplementedException();
IReadOnlyList<TContentBase> ImportContentBase<TContentBase, TContentTypeComposition>(
IEnumerable<CompiledPackageContentBase> docs,
IDictionary<string, TContentTypeComposition> importedDocumentTypes,

View File

@@ -0,0 +1,12 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services.ImportExport;
public interface IMemberTypeImportService
{
Task<Attempt<IMemberType?, MemberTypeImportOperationStatus>> Import(
Guid temporaryFileId,
Guid userKey,
Guid? mediaTypeId = null);
}

View File

@@ -0,0 +1,88 @@
using System.Xml.Linq;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.ContentEditing;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services.ImportExport;
public class MemberTypeImportService : IMemberTypeImportService
{
private readonly IPackageDataInstallation _packageDataInstallation;
private readonly IEntityService _entityService;
private readonly ITemporaryFileToXmlImportService _temporaryFileToXmlImportService;
private readonly ICoreScopeProvider _coreScopeProvider;
private readonly IUserIdKeyResolver _userIdKeyResolver;
public MemberTypeImportService(
IPackageDataInstallation packageDataInstallation,
IEntityService entityService,
ITemporaryFileToXmlImportService temporaryFileToXmlImportService,
ICoreScopeProvider coreScopeProvider,
IUserIdKeyResolver userIdKeyResolver)
{
_packageDataInstallation = packageDataInstallation;
_entityService = entityService;
_temporaryFileToXmlImportService = temporaryFileToXmlImportService;
_coreScopeProvider = coreScopeProvider;
_userIdKeyResolver = userIdKeyResolver;
}
public async Task<Attempt<IMemberType?, MemberTypeImportOperationStatus>> Import(
Guid temporaryFileId,
Guid userKey,
Guid? memberTypeId = null)
{
using ICoreScope scope = _coreScopeProvider.CreateCoreScope();
_temporaryFileToXmlImportService.CleanupFileIfScopeCompletes(temporaryFileId);
Attempt<XElement?, TemporaryFileXmlImportOperationStatus> loadXmlAttempt =
await _temporaryFileToXmlImportService.LoadXElementFromTemporaryFileAsync(temporaryFileId);
if (loadXmlAttempt.Success is false)
{
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
loadXmlAttempt.Status is TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound
? MemberTypeImportOperationStatus.TemporaryFileNotFound
: MemberTypeImportOperationStatus.TemporaryFileConversionFailure,
null);
}
Attempt<UmbracoEntityTypes> packageEntityTypeAttempt = _temporaryFileToXmlImportService.GetEntityType(loadXmlAttempt.Result!);
if (packageEntityTypeAttempt.Success is false ||
packageEntityTypeAttempt.Result is not UmbracoEntityTypes.MemberType)
{
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
MemberTypeImportOperationStatus.TypeMismatch,
null);
}
Guid packageEntityKey = _packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!);
if (memberTypeId is not null && memberTypeId.Equals(packageEntityKey) is false)
{
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
MemberTypeImportOperationStatus.IdMismatch,
null);
}
var entityExits = _entityService.Exists(
_packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!),
UmbracoObjectTypes.MemberType);
if (entityExits && memberTypeId is null)
{
return Attempt.FailWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
MemberTypeImportOperationStatus.MemberTypeExists,
null);
}
IReadOnlyList<IMemberType> importResult =
_packageDataInstallation.ImportMemberTypes(new[] { loadXmlAttempt.Result! }, await _userIdKeyResolver.GetAsync(userKey));
scope.Complete();
return Attempt.SucceedWithStatus<IMemberType?, MemberTypeImportOperationStatus>(
entityExits
? MemberTypeImportOperationStatus.SuccessUpdated
: MemberTypeImportOperationStatus.SuccessCreated,
importResult[0]);
}
}

View File

@@ -61,6 +61,8 @@ public class TemporaryFileToXmlImportService : ITemporaryFileToXmlImportService
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.DocumentType),
IEntityXmlSerializer.MediaTypeElementName
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MediaType),
IEntityXmlSerializer.MemberTypeElementName
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MemberType),
_ => Attempt<UmbracoEntityTypes>.Fail()
};
}

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Cms.Core.Services.OperationStatus;
public enum MemberTypeImportOperationStatus
{
SuccessCreated,
SuccessUpdated,
TemporaryFileNotFound,
TemporaryFileConversionFailure,
MemberTypeExists,
TypeMismatch,
IdMismatch
}

View File

@@ -41,6 +41,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
private readonly IEntityService _entityService;
private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService;
private readonly IMemberTypeService _memberTypeService;
public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory,
@@ -58,7 +59,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging
IMediaService mediaService,
IMediaTypeService mediaTypeService,
ITemplateContentParserService templateContentParserService,
ITemplateService templateService)
ITemplateService templateService,
IMemberTypeService memberTypeService)
{
_dataValueEditorFactory = dataValueEditorFactory;
_logger = logger;
@@ -76,10 +78,48 @@ namespace Umbraco.Cms.Infrastructure.Packaging
_mediaTypeService = mediaTypeService;
_templateContentParserService = templateContentParserService;
_templateService = templateService;
_memberTypeService = memberTypeService;
}
// Also remove factory service registration when this constructor is removed
[Obsolete("Use the constructor with Infrastructure.IScopeProvider and without global settings and hosting environment instead.")]
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory,
ILogger<PackageDataInstallation> logger,
IFileService fileService,
ILocalizationService localizationService,
IDataTypeService dataTypeService,
IEntityService entityService,
IContentTypeService contentTypeService,
IContentService contentService,
PropertyEditorCollection propertyEditors,
IScopeProvider scopeProvider,
IShortStringHelper shortStringHelper,
IConfigurationEditorJsonSerializer serializer,
IMediaService mediaService,
IMediaTypeService mediaTypeService,
ITemplateContentParserService templateContentParserService,
ITemplateService templateService)
: this(
dataValueEditorFactory,
logger,
fileService,
localizationService,
dataTypeService,
entityService,
contentTypeService,
contentService,
propertyEditors,
scopeProvider,
shortStringHelper,
serializer,
mediaService,
mediaTypeService,
templateContentParserService,
templateService,
StaticServiceProvider.Instance.GetRequiredService<IMemberTypeService>())
{ }
[Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory,
ILogger<PackageDataInstallation> logger,
@@ -107,7 +147,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
contentTypeService,
contentService,
propertyEditors,
(Umbraco.Cms.Infrastructure.Scoping.IScopeProvider)scopeProvider,
(IScopeProvider)scopeProvider,
shortStringHelper,
serializer,
mediaService,
@@ -163,27 +203,27 @@ namespace Umbraco.Cms.Infrastructure.Packaging
}
}
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="docTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <returns>An enumerable list of generated ContentTypes</returns>
/// <inheritdoc/>
public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId)
#pragma warning disable CS0618 // Type or member is obsolete
=> ImportMediaTypes(docTypeElements, userId, out _);
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Imports and saves package xml as <see cref="IContentType"/>
/// </summary>
/// <param name="docTypeElements">Xml to import</param>
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
/// <param name="entityContainersInstalled">Collection of entity containers installed by the package to be populated with those created in installing data types.</param>
/// <returns>An enumerable list of generated ContentTypes</returns>
[Obsolete("This method is not used in Umbraco outside of this class so will be made private in Umbraco 19.")]
public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId,
out IEnumerable<EntityContainer> entityContainersInstalled)
=> ImportDocumentTypes(docTypeElements.ToList(), true, userId, _mediaTypeService,
out entityContainersInstalled);
/// <inheritdoc/>
public IReadOnlyList<IMemberType> ImportMemberTypes(IEnumerable<XElement> docTypeElements, int userId)
=> ImportMemberTypes(docTypeElements, userId, out _);
private IReadOnlyList<IMemberType> ImportMemberTypes(IEnumerable<XElement> docTypeElements, int userId,
out IEnumerable<EntityContainer> entityContainersInstalled)
=> ImportDocumentTypes(docTypeElements.ToList(), true, userId, _memberTypeService,
out entityContainersInstalled);
#endregion
#region Content
@@ -805,26 +845,23 @@ namespace Umbraco.Cms.Infrastructure.Packaging
{
if (typeof(T) == typeof(IContentType))
{
if (parent is null)
{
return new ContentType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T;
}
else
{
return new ContentType(_shortStringHelper, (IContentType)parent, alias) { Key = key } as T;
}
return parent is null
? new ContentType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T
: new ContentType(_shortStringHelper, (IContentType)parent, alias) { Key = key } as T;
}
if (typeof(T) == typeof(IMediaType))
{
if (parent is null)
{
return new MediaType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T;
return parent is null
? new MediaType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T
: new MediaType(_shortStringHelper, (IMediaType)parent, alias) { Key = key } as T;
}
else
if (typeof(T) == typeof(IMemberType))
{
return new MediaType(_shortStringHelper, (IMediaType)parent, alias) { Key = key } as T;
}
return parent is null
? new MemberType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T
: new MemberType(_shortStringHelper, (IMemberType)parent, alias) { Key = key } as T;
}
throw new NotSupportedException($"Type {typeof(T)} is not supported");

File diff suppressed because one or more lines are too long

View File

@@ -122,6 +122,10 @@ export type CopyMediaTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type CopyMemberTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type CreateDataTypeRequestModel = {
name: string;
editorAlias: string;
@@ -327,6 +331,7 @@ export type CreateMemberTypeRequestModel = {
properties: Array<CreateMemberTypePropertyTypeRequestModel>;
containers: Array<CreateMemberTypePropertyTypeContainerRequestModel>;
id?: string | null;
parent?: ReferenceByIdModel | null;
compositions: Array<MemberTypeCompositionModel>;
};
@@ -1110,6 +1115,10 @@ export type ImportMediaTypeRequestModel = {
file: ReferenceByIdModel;
};
export type ImportMemberTypeRequestModel = {
file: ReferenceByIdModel;
};
export type IndexResponseModel = {
name: string;
healthStatus: HealthStatusResponseModel;
@@ -1566,6 +1575,7 @@ export type MemberTypeTreeItemResponseModel = {
parent?: ReferenceByIdModel | null;
flags: Array<FlagModel>;
name: string;
isFolder: boolean;
icon: string;
};
@@ -1636,6 +1646,10 @@ export type MoveMediaTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type MoveMemberTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type NamedEntityTreeItemResponseModel = {
hasChildren: boolean;
id: string;
@@ -2923,7 +2937,7 @@ export type UserInstallRequestModel = {
name: string;
email: string;
password: string;
readonly subscribeToNewsletter: boolean;
subscribeToNewsletter: boolean;
};
export type UserItemResponseModel = {
@@ -3158,12 +3172,6 @@ export type UpgradeSettingsResponseModelWritable = {
oldVersion: string;
};
export type UserInstallRequestModelWritable = {
name: string;
email: string;
password: string;
};
export type GetCultureData = {
body?: never;
path?: never;
@@ -10718,7 +10726,7 @@ export type GetMemberTypeByIdCompositionReferencesResponses = {
export type GetMemberTypeByIdCompositionReferencesResponse = GetMemberTypeByIdCompositionReferencesResponses[keyof GetMemberTypeByIdCompositionReferencesResponses];
export type PostMemberTypeByIdCopyData = {
body?: never;
body?: CopyMemberTypeRequestModel;
path: {
id: string;
};
@@ -10754,6 +10762,115 @@ export type PostMemberTypeByIdCopyResponses = {
201: unknown;
};
export type GetMemberTypeByIdExportData = {
body?: never;
path: {
id: string;
};
query?: never;
url: '/umbraco/management/api/v1/member-type/{id}/export';
};
export type GetMemberTypeByIdExportErrors = {
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
/**
* Not Found
*/
404: ProblemDetails;
};
export type GetMemberTypeByIdExportError = GetMemberTypeByIdExportErrors[keyof GetMemberTypeByIdExportErrors];
export type GetMemberTypeByIdExportResponses = {
/**
* OK
*/
200: Blob | File;
};
export type GetMemberTypeByIdExportResponse = GetMemberTypeByIdExportResponses[keyof GetMemberTypeByIdExportResponses];
export type PutMemberTypeByIdImportData = {
body?: ImportMemberTypeRequestModel;
path: {
id: string;
};
query?: never;
url: '/umbraco/management/api/v1/member-type/{id}/import';
};
export type PutMemberTypeByIdImportErrors = {
/**
* Bad Request
*/
400: ProblemDetails;
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
/**
* Not Found
*/
404: ProblemDetails;
};
export type PutMemberTypeByIdImportError = PutMemberTypeByIdImportErrors[keyof PutMemberTypeByIdImportErrors];
export type PutMemberTypeByIdImportResponses = {
/**
* OK
*/
200: unknown;
};
export type PutMemberTypeByIdMoveData = {
body?: MoveMemberTypeRequestModel;
path: {
id: string;
};
query?: never;
url: '/umbraco/management/api/v1/member-type/{id}/move';
};
export type PutMemberTypeByIdMoveErrors = {
/**
* Bad Request
*/
400: ProblemDetails;
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
/**
* Not Found
*/
404: ProblemDetails;
};
export type PutMemberTypeByIdMoveError = PutMemberTypeByIdMoveErrors[keyof PutMemberTypeByIdMoveErrors];
export type PutMemberTypeByIdMoveResponses = {
/**
* OK
*/
200: unknown;
};
export type PostMemberTypeAvailableCompositionsData = {
body?: MemberTypeCompositionRequestModel;
path?: never;
@@ -10952,12 +11069,109 @@ export type PutMemberTypeFolderByIdResponses = {
200: unknown;
};
export type PostMemberTypeImportData = {
body?: ImportMemberTypeRequestModel;
path?: never;
query?: never;
url: '/umbraco/management/api/v1/member-type/import';
};
export type PostMemberTypeImportErrors = {
/**
* Bad Request
*/
400: ProblemDetails;
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
/**
* Not Found
*/
404: ProblemDetails;
};
export type PostMemberTypeImportError = PostMemberTypeImportErrors[keyof PostMemberTypeImportErrors];
export type PostMemberTypeImportResponses = {
/**
* Created
*/
201: unknown;
};
export type GetTreeMemberTypeAncestorsData = {
body?: never;
path?: never;
query?: {
descendantId?: string;
};
url: '/umbraco/management/api/v1/tree/member-type/ancestors';
};
export type GetTreeMemberTypeAncestorsErrors = {
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
};
export type GetTreeMemberTypeAncestorsResponses = {
/**
* OK
*/
200: Array<MemberTypeTreeItemResponseModel>;
};
export type GetTreeMemberTypeAncestorsResponse = GetTreeMemberTypeAncestorsResponses[keyof GetTreeMemberTypeAncestorsResponses];
export type GetTreeMemberTypeChildrenData = {
body?: never;
path?: never;
query?: {
parentId?: string;
skip?: number;
take?: number;
foldersOnly?: boolean;
};
url: '/umbraco/management/api/v1/tree/member-type/children';
};
export type GetTreeMemberTypeChildrenErrors = {
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user does not have access to this resource
*/
403: unknown;
};
export type GetTreeMemberTypeChildrenResponses = {
/**
* OK
*/
200: PagedMemberTypeTreeItemResponseModel;
};
export type GetTreeMemberTypeChildrenResponse = GetTreeMemberTypeChildrenResponses[keyof GetTreeMemberTypeChildrenResponses];
export type GetTreeMemberTypeRootData = {
body?: never;
path?: never;
query?: {
skip?: number;
take?: number;
foldersOnly?: boolean;
};
url: '/umbraco/management/api/v1/tree/member-type/root';
};
@@ -10989,6 +11203,7 @@ export type GetTreeMemberTypeSiblingsData = {
target?: string;
before?: number;
after?: number;
foldersOnly?: boolean;
};
url: '/umbraco/management/api/v1/tree/member-type/siblings';
};

View File

@@ -6,7 +6,7 @@ import type { MetaEntityCreateOptionAction } from '@umbraco-cms/backoffice/entit
export class UmbDefaultMediaTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() {
const parentEntityType = this.args.entityType as UmbMediaTypeRootEntityType | UmbMediaTypeFolderEntityType;
if (!parentEntityType) throw new Error('Entity type is required to create a document type');
if (!parentEntityType) throw new Error('Entity type is required to create a media type');
const parentUnique = this.args.unique ?? null;

View File

@@ -8,4 +8,8 @@ export * from './search/constants.js';
export * from './tree/constants.js';
export * from './workspace/constants.js';
export { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_ENTITY_TYPE } from './entity.js';
export {
UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE,
UMB_MEMBER_TYPE_ENTITY_TYPE,
UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
} from './entity.js';

View File

@@ -1 +1,4 @@
export * from './duplicate/constants.js';
export * from './export/constants.js';
export * from './import/constants.js';
export * from './move-to/constants.js';

View File

@@ -1,11 +1,11 @@
import type { UmbMemberTypeRootEntityType } from '../../../entity.js';
import type { UmbMemberTypeFolderEntityType, UmbMemberTypeRootEntityType } from '../../../entity.js';
import { UMB_CREATE_MEMBER_TYPE_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action';
import type { MetaEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
export class UmbDefaultMemberTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() {
const parentEntityType = this.args.entityType as UmbMemberTypeRootEntityType;
const parentEntityType = this.args.entityType as UmbMemberTypeRootEntityType | UmbMemberTypeFolderEntityType;
if (!parentEntityType) throw new Error('Entity type is required to create a member type');
const parentUnique = this.args.unique ?? null;

View File

@@ -1,4 +1,4 @@
import { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../../../entity.js';
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../../../entity.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -8,10 +8,12 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
name: 'Default Member Type Entity Create Option Action',
weight: 1000,
api: () => import('./default-member-type-create-option-action.js'),
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE],
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE],
meta: {
icon: 'icon-user',
label: '#content_membertype',
},
},
];

View File

@@ -0,0 +1,19 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../../../entity.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS } from '../../../tree/folder/constants.js';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
{
type: 'entityCreateOptionAction',
kind: 'folder',
alias: 'Umb.EntityCreateOptionAction.MemberType.Folder',
name: 'Member Type Folder Entity Create Option Action',
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE],
meta: {
icon: 'icon-folder',
label: '#create_folder',
additionalOptions: true,
folderRepositoryAlias: UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS,
},
},
];

View File

@@ -1,5 +1,10 @@
import { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js';
import {
UMB_MEMBER_TYPE_ENTITY_TYPE,
UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE,
} from '../../entity.js';
import { manifests as defaultManifests } from './default/manifests.js';
import { manifests as folderManifests } from './folder/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -8,7 +13,9 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
kind: 'create',
alias: 'Umb.EntityAction.MemberType.Create',
name: 'Create Member Type Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE],
weight: 1200,
forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE],
},
...defaultManifests,
...folderManifests,
];

View File

@@ -1,18 +1,23 @@
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../../entity.js';
import { UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS } from '../../constants.js';
import { UMB_DUPLICATE_MEMBER_TYPE_REPOSITORY_ALIAS } from './repository/index.js';
import {
UMB_MEMBER_TYPE_ENTITY_TYPE,
UMB_MEMBER_TYPE_TREE_ALIAS,
UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS,
} from '../../constants.js';
import { UMB_DUPLICATE_MEMBER_TYPE_REPOSITORY_ALIAS } from './constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'duplicate',
alias: 'Umb.EntityAction.MemberType.Duplicate',
name: 'Duplicate Member Type Entity Action',
kind: 'duplicateTo',
alias: 'Umb.EntityAction.MemberType.DuplicateTo',
name: 'Duplicate Document To Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE],
meta: {
duplicateRepositoryAlias: UMB_DUPLICATE_MEMBER_TYPE_REPOSITORY_ALIAS,
treeAlias: UMB_MEMBER_TYPE_TREE_ALIAS,
treeRepositoryAlias: UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS,
foldersOnly: true,
},
},
...repositoryManifests,

View File

@@ -1,13 +1,13 @@
import { UmbDuplicateMemberTypeServerDataSource } from './member-type-duplicate.server.data-source.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import type { UmbDuplicateRepository, UmbDuplicateRequestArgs } from '@umbraco-cms/backoffice/entity-action';
import type { UmbDuplicateToRepository, UmbDuplicateToRequestArgs } from '@umbraco-cms/backoffice/tree';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbDuplicateMemberTypeRepository extends UmbRepositoryBase implements UmbDuplicateRepository {
export class UmbDuplicateMemberTypeRepository extends UmbRepositoryBase implements UmbDuplicateToRepository {
#duplicateSource = new UmbDuplicateMemberTypeServerDataSource(this);
async requestDuplicate(args: UmbDuplicateRequestArgs) {
const { error } = await this.#duplicateSource.duplicate(args);
async requestDuplicateTo(args: UmbDuplicateToRequestArgs) {
const { error } = await this.#duplicateSource.duplicateTo(args);
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);

View File

@@ -1,13 +1,13 @@
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import type { UmbDuplicateDataSource, UmbDuplicateRequestArgs } from '@umbraco-cms/backoffice/entity-action';
import type { UmbDuplicateToDataSource, UmbDuplicateToRequestArgs } from '@umbraco-cms/backoffice/tree';
/**
* Duplicate Document Server Data Source
* Duplicate Member Type Server Data Source
* @class UmbDuplicateMemberTypeServerDataSource
*/
export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateDataSource {
export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateToDataSource {
#host: UmbControllerHost;
/**
@@ -20,18 +20,22 @@ export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateDataS
}
/**
* Duplicate an item for the given unique
* @param {UmbDuplicateRequestArgs} args
* Duplicate an item for the given unique to the destination unique
* @param {UmbDuplicateToRequestArgs} args
* @returns {*}
* @memberof UmbDuplicateDataTypeServerDataSource
* @memberof UmbDuplicateMemberTypeServerDataSource
*/
async duplicate(args: UmbDuplicateRequestArgs) {
async duplicateTo(args: UmbDuplicateToRequestArgs) {
if (!args.unique) throw new Error('Unique is missing');
if (args.destination.unique === undefined) throw new Error('Destination unique is missing');
return tryExecute(
this.#host,
MemberTypeService.postMemberTypeByIdCopy({
path: { id: args.unique },
body: {
target: args.destination.unique ? { id: args.destination.unique } : null,
},
}),
);
}

View File

@@ -0,0 +1 @@
export * from './repository/constants.js';

View File

@@ -0,0 +1,19 @@
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../../entity.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'default',
alias: 'Umb.EntityAction.MemberType.Export',
name: 'Export Member Type Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE],
api: () => import('./member-type-export.action.js'),
meta: {
icon: 'icon-download-alt',
label: '#actions_export',
additionalOptions: true,
},
},
...repositoryManifests,
];

View File

@@ -0,0 +1,21 @@
import { UmbExportMemberTypeRepository } from './repository/index.js';
import { blobDownload } from '@umbraco-cms/backoffice/utils';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
export class UmbExportMemberTypeEntityAction extends UmbEntityActionBase<object> {
#repository = new UmbExportMemberTypeRepository(this);
override async execute() {
if (!this.args.unique) throw new Error('Unique is not available');
const { data, error } = await this.#repository.requestExport(this.args.unique);
if (error) {
throw error;
}
blobDownload(data, `${this.args.unique}.udt`, 'text/xml');
}
}
export default UmbExportMemberTypeEntityAction;

View File

@@ -0,0 +1 @@
export const UMB_EXPORT_MEMBER_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MemberType.Export';

View File

@@ -0,0 +1,2 @@
export { UmbExportMemberTypeRepository } from './member-type-export.repository.js';
export { UMB_EXPORT_MEMBER_TYPE_REPOSITORY_ALIAS } from './constants.js';

View File

@@ -0,0 +1,10 @@
import { UMB_EXPORT_MEMBER_TYPE_REPOSITORY_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_EXPORT_MEMBER_TYPE_REPOSITORY_ALIAS,
name: 'Export Member Type Repository',
api: () => import('./member-type-export.repository.js'),
},
];

View File

@@ -0,0 +1,24 @@
import { UmbExportMemberTypeServerDataSource } from './member-type-export.server.data-source.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbExportMemberTypeRepository extends UmbRepositoryBase {
#exportSource = new UmbExportMemberTypeServerDataSource(this);
async requestExport(unique: string) {
const { data, error } = await this.#exportSource.export(unique);
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Exported` } };
notificationContext.peek('positive', notification);
}
return { data, error };
}
}
export { UmbExportMemberTypeRepository as api };

View File

@@ -0,0 +1,33 @@
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
/**
* Export Member Server Data Source
* @export
* @class UmbExportMemberTypeServerDataSource
*/
export class UmbExportMemberTypeServerDataSource {
#host: UmbControllerHost;
/**
* Creates an instance of UmbExportMemberTypeServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbExportMemberTypeServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Export an item for the given id to the destination unique
* @param {unique} unique
* @returns {*}
* @memberof UmbExportMemberTypeServerDataSource
*/
async export(unique: string) {
if (!unique) throw new Error('Unique is missing');
return tryExecute(this.#host, MemberTypeService.getMemberTypeByIdExport({ path: { id: unique } }));
}
}

View File

@@ -0,0 +1,2 @@
export * from './modal/constants.js';
export * from './repository/constants.js';

View File

@@ -0,0 +1,21 @@
import { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as modalManifests } from './modal/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'default',
alias: 'Umb.EntityAction.MemberType.Import',
name: 'Export Member Type Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE],
api: () => import('./member-type-import.action.js'),
meta: {
icon: 'icon-page-up',
label: '#actions_import',
additionalOptions: true,
},
},
...repositoryManifests,
...modalManifests,
];

View File

@@ -0,0 +1,25 @@
import { UMB_MEMBER_TYPE_IMPORT_MODAL } from './modal/member-type-import-modal.token.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbImportMemberTypeEntityAction extends UmbEntityActionBase<object> {
override async execute() {
await umbOpenModal(this, UMB_MEMBER_TYPE_IMPORT_MODAL, {
data: { unique: this.args.unique },
}).catch(() => {});
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Action event context not found.');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,
});
actionEventContext.dispatchEvent(event);
}
}
export default UmbImportMemberTypeEntityAction;

View File

@@ -0,0 +1 @@
export * from './member-type-import-modal.token.js';

View File

@@ -0,0 +1,10 @@
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';
export const manifests: Array<ManifestModal> = [
{
type: 'modal',
alias: 'Umb.Modal.MemberType.Import',
name: 'Member Type Import Modal',
element: () => import('./member-type-import-modal.element.js'),
},
];

View File

@@ -0,0 +1,171 @@
import { UmbMemberTypeImportRepository } from '../repository/member-type-import.repository.js';
import type { UmbMemberTypeImportModalData, UmbMemberTypeImportModalValue } from './member-type-import-modal.token.js';
import { css, html, customElement, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { UmbDropzoneMediaElement } from '@umbraco-cms/backoffice/media';
import type { UmbDropzoneChangeEvent } from '@umbraco-cms/backoffice/dropzone';
interface UmbMemberTypePreview {
unique: string;
name: string;
alias: string;
icon: string;
}
@customElement('umb-member-type-import-modal')
export class UmbMemberTypeImportModalLayout extends UmbModalBaseElement<
UmbMemberTypeImportModalData,
UmbMemberTypeImportModalValue
> {
#memberTypeImportRepository = new UmbMemberTypeImportRepository(this);
#temporaryUnique?: string;
#fileReader;
@state()
private _fileContent: Array<UmbMemberTypePreview> = [];
constructor() {
super();
this.#fileReader = new FileReader();
this.#fileReader.onload = (e) => {
if (typeof e.target?.result === 'string') {
const fileContent = e.target.result;
this.#memberTypePreviewBuilder(fileContent);
} else {
this.#requestReset();
}
};
}
#onUploadComplete(evt: UmbDropzoneChangeEvent) {
evt.preventDefault();
const target = evt.target as UmbDropzoneMediaElement;
const data = target.value;
if (!data?.length) return;
const file = data[0];
if (file.temporaryFile) {
this.#temporaryUnique = file.temporaryFile.temporaryUnique;
this.#fileReader.readAsText(file.temporaryFile.file);
}
}
async #onFileImport() {
if (!this.#temporaryUnique) return;
const { error } = await this.#memberTypeImportRepository.requestImport(this.#temporaryUnique);
if (error) return;
this._submitModal();
}
#memberTypePreviewBuilder(htmlString: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/xml');
const childNodes = doc.childNodes;
const elements: Array<Element> = [];
childNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'MemberType') {
elements.push(node as Element);
}
});
this._fileContent = this.#memberTypePreviewItemBuilder(elements);
}
#memberTypePreviewItemBuilder(elements: Array<Element>) {
const memberTypes: Array<UmbMemberTypePreview> = [];
elements.forEach((MemberType) => {
const info = MemberType.getElementsByTagName('Info')[0];
const unique = info.getElementsByTagName('Key')[0].textContent ?? '';
const name = info.getElementsByTagName('Name')[0].textContent ?? '';
const alias = info.getElementsByTagName('Alias')[0].textContent ?? '';
const icon = info.getElementsByTagName('Icon')[0].textContent ?? '';
memberTypes.push({ unique, name, alias, icon });
});
return memberTypes;
}
#requestReset() {
this._fileContent = [];
this.#temporaryUnique = undefined;
}
override render() {
return html` <umb-body-layout headline=${this.localize.term('general_import')}>
<uui-box> ${this.#renderUploadZone()} </uui-box>
<uui-button
slot="actions"
type="button"
label=${this.localize.term('general_cancel')}
@click=${this._rejectModal}></uui-button>
<uui-button
slot="actions"
type="button"
look="primary"
?disabled=${!this.#temporaryUnique}
label=${this.localize.term('actions_import')}
@click=${this.#onFileImport}></uui-button>
</umb-body-layout>`;
}
#renderUploadZone() {
return html`
${when(
this._fileContent.length,
() =>
html`<uui-ref-node name=${this._fileContent[0].name} alias=${this._fileContent[0].alias} readonly standalone>
<umb-icon slot="icon" name=${this._fileContent[0].icon}></umb-icon>
<uui-button
slot="actions"
@click=${this.#requestReset}
label=${this.localize.term('general_remove')}></uui-button>
</uui-ref-node>`,
() =>
html`<div id="wrapper">
<umb-input-dropzone id="dropzone" accept=".udt" @change=${this.#onUploadComplete}
><umb-localize slot="text" key="member_dragAndDropYourFilesIntoTheArea"
>Drag and drop your file(s) into the area
</umb-localize></umb-input-dropzone
>
</div>`,
)}
`;
}
static override styles = [
UmbTextStyles,
css`
#wrapper {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
gap: var(--uui-size-space-3);
border: 2px dashed var(--uui-color-divider-standalone);
background-color: var(--uui-color-surface-alt);
padding: var(--uui-size-space-6);
}
#dropzone {
width: 100%;
}
#import {
margin-top: var(--uui-size-space-6);
}
`,
];
}
export default UmbMemberTypeImportModalLayout;
declare global {
interface HTMLElementTagNameMap {
'umb-member-type-import-modal': UmbMemberTypeImportModalLayout;
}
}

View File

@@ -0,0 +1,18 @@
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbMemberTypeImportModalData {
unique: string | null;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface UmbMemberTypeImportModalValue {}
export const UMB_MEMBER_TYPE_IMPORT_MODAL = new UmbModalToken<UmbMemberTypeImportModalData, UmbMemberTypeImportModalValue>(
'Umb.Modal.MemberType.Import',
{
modal: {
type: 'sidebar',
size: 'small',
},
},
);

View File

@@ -0,0 +1 @@
export const UMB_MEMBER_TYPE_IMPORT_REPOSITORY_ALIAS = 'Umb.Repository.MemberType.Import';

View File

@@ -0,0 +1,2 @@
export { UmbMemberTypeImportRepository } from './member-type-import.repository.js';
export { UMB_MEMBER_TYPE_IMPORT_REPOSITORY_ALIAS } from './constants.js';

View File

@@ -0,0 +1,10 @@
import { UMB_MEMBER_TYPE_IMPORT_REPOSITORY_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_MEMBER_TYPE_IMPORT_REPOSITORY_ALIAS,
name: 'Import Member Type Repository',
api: () => import('./member-type-import.repository.js'),
},
];

View File

@@ -0,0 +1,24 @@
import { UmbMemberTypeImportServerDataSource } from './member-type-import.server.data-source.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbMemberTypeImportRepository extends UmbRepositoryBase {
#importSource = new UmbMemberTypeImportServerDataSource(this);
async requestImport(temporaryUnique: string) {
const { data, error } = await this.#importSource.import(temporaryUnique);
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Imported` } };
notificationContext.peek('positive', notification);
}
return { data, error };
}
}
export { UmbMemberTypeImportRepository as api };

View File

@@ -0,0 +1,37 @@
import { MemberTypeService, type ImportMemberTypeRequestModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
/**
* Member Type Import Server Data Source
* @Import
* @class UmbMemberTypeImportServerDataSource
*/
export class UmbMemberTypeImportServerDataSource {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberTypeImportServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbMemberTypeImportServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Import an item for the given id to the destination unique
* @param {temporaryUnique} temporaryUnique
* @returns {*}
* @memberof UmbMemberTypeImportServerDataSource
*/
async import(temporaryUnique: string) {
if (!temporaryUnique) throw new Error('Unique is missing');
const body: ImportMemberTypeRequestModel = {
file: { id: temporaryUnique },
};
return tryExecute(this.#host, MemberTypeService.postMemberTypeImport({ body }));
}
}

View File

@@ -1,7 +1,13 @@
import { UMB_MEMBER_TYPE_DETAIL_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_ITEM_REPOSITORY_ALIAS } from '../constants.js';
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../entity.js';
import {
UMB_MEMBER_TYPE_ENTITY_TYPE,
UMB_MEMBER_TYPE_DETAIL_REPOSITORY_ALIAS,
UMB_MEMBER_TYPE_ITEM_REPOSITORY_ALIAS,
} from '../constants.js';
import { manifests as createManifests } from './create/manifests.js';
import { manifests as moveManifests } from './move-to/manifests.js';
import { manifests as duplicateManifests } from './duplicate/manifests.js';
import { manifests as exportManifests } from './export/manifests.js';
import { manifests as importManifests } from './import/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -17,5 +23,8 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
},
},
...createManifests,
...moveManifests,
...duplicateManifests,
...exportManifests,
...importManifests,
];

View File

@@ -0,0 +1 @@
export * from './repository/constants.js';

View File

@@ -0,0 +1 @@
export { UmbMoveMemberTypeRepository } from './repository/index.js';

View File

@@ -0,0 +1,21 @@
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../../entity.js';
import { UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_TREE_ALIAS } from '../../constants.js';
import { UMB_MOVE_MEMBER_TYPE_REPOSITORY_ALIAS } from './constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'moveTo',
alias: 'Umb.EntityAction.MemberType.MoveTo',
name: 'Move Member Type Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_ENTITY_TYPE],
meta: {
treeRepositoryAlias: UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS,
moveRepositoryAlias: UMB_MOVE_MEMBER_TYPE_REPOSITORY_ALIAS,
treeAlias: UMB_MEMBER_TYPE_TREE_ALIAS,
foldersOnly: true,
},
},
...repositoryManifests,
];

View File

@@ -0,0 +1 @@
export const UMB_MOVE_MEMBER_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MemberType.Move';

View File

@@ -0,0 +1 @@
export { UmbMoveMemberTypeRepository } from './member-type-move.repository.js';

View File

@@ -0,0 +1,10 @@
import { UMB_MOVE_MEMBER_TYPE_REPOSITORY_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_MOVE_MEMBER_TYPE_REPOSITORY_ALIAS,
name: 'Move Member Type Repository',
api: () => import('./member-type-move.repository.js'),
},
];

View File

@@ -0,0 +1,25 @@
import { UmbMoveMemberTypeServerDataSource } from './member-type-move.server.data-source.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import type { UmbMoveRepository, UmbMoveToRequestArgs } from '@umbraco-cms/backoffice/tree';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbMoveMemberTypeRepository extends UmbRepositoryBase implements UmbMoveRepository {
#moveSource = new UmbMoveMemberTypeServerDataSource(this);
async requestMoveTo(args: UmbMoveToRequestArgs) {
const { error } = await this.#moveSource.moveTo(args);
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error(`Failed to load notification context`);
}
const notification = { data: { message: `Moved` } };
notificationContext.peek('positive', notification);
}
return { error };
}
}
export { UmbMoveMemberTypeRepository as api };

View File

@@ -0,0 +1,44 @@
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import type { UmbMoveDataSource, UmbMoveToRequestArgs } from '@umbraco-cms/backoffice/tree';
/**
* Move Member Type Server Data Source
* @class UmbMoveMemberTypeServerDataSource
*/
export class UmbMoveMemberTypeServerDataSource implements UmbMoveDataSource {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMoveMemberTypeServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMoveMemberTypeServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Move an item for the given id to the target unique
* @param {string} unique
* @param {(string | null)} targetUnique
* @param args
* @returns {*}
* @memberof UmbMoveMemberTypeServerDataSource
*/
async moveTo(args: UmbMoveToRequestArgs) {
if (!args.unique) throw new Error('Unique is missing');
if (args.destination.unique === undefined) throw new Error('Destination unique is missing');
return tryExecute(
this.#host,
MemberTypeService.putMemberTypeByIdMove({
path: { id: args.unique },
body: {
target: args.destination.unique ? { id: args.destination.unique } : null,
},
}),
);
}
}

View File

@@ -1,5 +1,7 @@
export const UMB_MEMBER_TYPE_ENTITY_TYPE = 'member-type';
export const UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE = 'member-type-root';
export const UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE = 'member-type-folder';
export type UmbMemberTypeEntityType = typeof UMB_MEMBER_TYPE_ENTITY_TYPE;
export type UmbMemberTypeRootEntityType = typeof UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE;
export type UmbMemberTypeFolderEntityType = typeof UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE;

View File

@@ -130,7 +130,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailDataSource<
* @returns {*}
* @memberof UmbMemberTypeDetailServerDataSource
*/
async create(model: UmbMemberTypeDetailModel) {
async create(model: UmbMemberTypeDetailModel, parentUnique: string | null = null) {
if (!model) throw new Error('Member Type is missing');
// TODO: make data mapper to prevent errors
@@ -162,6 +162,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailDataSource<
}),
containers: model.containers,
id: model.unique,
parent: parentUnique ? { id: parentUnique } : null,
compositions: model.compositions.map((composition) => {
return {
memberType: { id: composition.contentType.unique },

View File

@@ -1,7 +1,7 @@
import type { UmbMemberTypeEntityType } from '../../entity.js';
import type { UmbMemberTypeEntityType, UmbMemberTypeFolderEntityType } from '../../entity.js';
export interface UmbMemberTypeItemModel {
entityType: UmbMemberTypeEntityType;
entityType: UmbMemberTypeEntityType | UmbMemberTypeFolderEntityType;
unique: string;
name: string;
icon: string;

View File

@@ -7,4 +7,5 @@ export const UMB_MEMBER_TYPE_TREE_ALIAS = 'Umb.Tree.MemberType';
export { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT } from './member-type-tree.store.context-token.js';
export * from './folder/constants.js';
export * from './tree-item-children/constants.js';

View File

@@ -0,0 +1,2 @@
export * from './repository/constants.js';
export * from './workspace/constants.js';

View File

@@ -0,0 +1,29 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../entity.js';
import { UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS } from './repository/constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'folderUpdate',
alias: 'Umb.EntityAction.MemberType.Folder.Update',
name: 'Rename Member Type Folder Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE],
meta: {
folderRepositoryAlias: UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS,
},
},
{
type: 'entityAction',
kind: 'folderDelete',
alias: 'Umb.EntityAction.MemberType.Folder.Delete',
name: 'Delete Member Type Folder Entity Action',
forEntityTypes: [UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE],
meta: {
folderRepositoryAlias: UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS,
},
},
...repositoryManifests,
...workspaceManifests,
];

View File

@@ -0,0 +1,4 @@
export const UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS = 'Umb.Repository.MemberType.Folder';
export const UMB_MEMBER_TYPE_FOLDER_STORE_ALIAS = 'Umb.Store.MemberType.Folder';
export * from './member-type-folder.repository.js';
export { UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT } from './member-type-folder.store.context-token.js';

View File

@@ -0,0 +1 @@
export * from './member-type-folder.repository.js';

View File

@@ -0,0 +1,16 @@
import { UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_FOLDER_STORE_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'repository',
alias: UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS,
name: 'Member Type Folder Repository',
api: () => import('./member-type-folder.repository.js'),
},
{
type: 'store',
alias: UMB_MEMBER_TYPE_FOLDER_STORE_ALIAS,
name: 'Member Type Folder Store',
api: () => import('./member-type-folder.store.js'),
},
];

View File

@@ -0,0 +1,13 @@
import { UmbMemberTypeFolderServerDataSource } from './member-type-folder.server.data-source.js';
import { UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT } from './member-type-folder.store.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
import type { UmbFolderModel } from '@umbraco-cms/backoffice/tree';
export class UmbMemberTypeFolderRepository extends UmbDetailRepositoryBase<UmbFolderModel> {
constructor(host: UmbControllerHost) {
super(host, UmbMemberTypeFolderServerDataSource, UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT);
}
}
export default UmbMemberTypeFolderRepository;

View File

@@ -0,0 +1,144 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js';
import type { UmbFolderModel } from '@umbraco-cms/backoffice/tree';
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
/**
* A data source for a Member Type folder that fetches data from the server
* @class UmbMemberTypeFolderServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbMemberTypeFolderServerDataSource implements UmbDetailDataSource<UmbFolderModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberTypeFolderServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMemberTypeFolderServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Creates a scaffold for a Member Type folder
* @param {Partial<UmbFolderModel>} [preset]
* @returns {*}
* @memberof UmbMemberTypeFolderServerDataSource
*/
async createScaffold(preset?: Partial<UmbFolderModel>) {
const scaffold: UmbFolderModel = {
entityType: UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
unique: UmbId.new(),
name: '',
...preset,
};
return { data: scaffold };
}
/**
* Fetches a Member Type folder from the server
* @param {string} unique
* @returns {*}
* @memberof UmbMemberTypeFolderServerDataSource
*/
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(
this.#host,
MemberTypeService.getMemberTypeFolderById({
path: { id: unique },
}),
);
if (data) {
const mappedData = {
entityType: UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
unique: data.id,
name: data.name,
};
return { data: mappedData };
}
return { error };
}
/**
* Creates a Member Type folder on the server
* @param {UmbFolderModel} model
* @returns {*}
* @memberof UmbMemberTypeFolderServerDataSource
*/
async create(model: UmbFolderModel, parentUnique: string | null) {
if (!model) throw new Error('Data is missing');
if (!model.unique) throw new Error('Unique is missing');
if (!model.name) throw new Error('Name is missing');
const body = {
id: model.unique,
parent: parentUnique ? { id: parentUnique } : null,
name: model.name,
};
const { error } = await tryExecute(
this.#host,
MemberTypeService.postMemberTypeFolder({
body,
}),
);
if (!error) {
return this.read(model.unique);
}
return { error };
}
/**
* Updates a Member Type folder on the server
* @param {UmbUpdateFolderModel} model
* @returns {*}
* @memberof UmbMemberTypeFolderServerDataSource
*/
async update(model: UmbFolderModel) {
if (!model) throw new Error('Data is missing');
if (!model.unique) throw new Error('Unique is missing');
if (!model.name) throw new Error('Folder name is missing');
const { error } = await tryExecute(
this.#host,
MemberTypeService.putMemberTypeFolderById({
path: { id: model.unique },
body: { name: model.name },
}),
);
if (!error) {
return this.read(model.unique);
}
return { error };
}
/**
* Deletes a Member Type folder on the server
* @param {string} unique
* @returns {*}
* @memberof UmbMemberTypeServerDataSource
*/
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');
return tryExecute(
this.#host,
MemberTypeService.deleteMemberTypeFolderById({
path: { id: unique },
}),
);
}
}

View File

@@ -0,0 +1,6 @@
import type { UmbMemberTypeFolderStore } from './member-type-folder.store.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT = new UmbContextToken<UmbMemberTypeFolderStore>(
'UmbMemberTypeFolderStore',
);

View File

@@ -0,0 +1,22 @@
import { UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT } from './member-type-folder.store.context-token.js';
import { UmbDetailStoreBase } from '@umbraco-cms/backoffice/store';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbFolderModel } from '@umbraco-cms/backoffice/tree';
/**
* @class UmbMemberTypeStore
* @augments {UmbStoreBase}
* @description - Data Store for Member Types
*/
export class UmbMemberTypeFolderStore extends UmbDetailStoreBase<UmbFolderModel> {
/**
* Creates an instance of UmbMemberTypeStore.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMemberTypeStore
*/
constructor(host: UmbControllerHost) {
super(host, UMB_MEMBER_TYPE_FOLDER_STORE_CONTEXT.toString());
}
}
export { UmbMemberTypeFolderStore as api };

View File

@@ -0,0 +1,6 @@
import type { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../entity.js';
import type { UmbMemberTypeTreeItemModel } from '../types.js';
export interface UmbMemberTypeFolderTreeItemModel extends UmbMemberTypeTreeItemModel {
entityType: typeof UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE;
}

View File

@@ -0,0 +1,3 @@
export const UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS = 'Umb.Workspace.MemberType.Folder';
export * from './member-type-folder.workspace.context-token.js';
export * from './paths.js';

View File

@@ -0,0 +1,34 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js';
import { UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'workspace',
kind: 'routable',
alias: UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS,
name: 'Member Type Folder Workspace',
api: () => import('./member-type-folder-workspace.context.js'),
meta: {
entityType: UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
},
},
{
type: 'workspaceAction',
kind: 'default',
alias: 'Umb.WorkspaceAction.MemberType.Folder.Submit',
name: 'Submit Member Type Folder Workspace Action',
api: UmbSubmitWorkspaceAction,
meta: {
label: '#buttons_save',
look: 'primary',
color: 'positive',
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},
];

View File

@@ -0,0 +1,17 @@
import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-member-type-folder-workspace-editor')
export class UmbMemberTypeFolderWorkspaceEditorElement extends UmbLitElement {
override render() {
return html`<umb-folder-workspace-editor></umb-folder-workspace-editor>`;
}
}
export { UmbMemberTypeFolderWorkspaceEditorElement as element };
declare global {
interface HTMLElementTagNameMap {
['umb-member-type-folder-workspace-editor']: UmbMemberTypeFolderWorkspaceEditorElement;
}
}

View File

@@ -0,0 +1,39 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js';
import { UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS } from '../constants.js';
import type { UmbMemberTypeFolderRepository } from '../repository/index.js';
import { UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js';
import { UmbMemberTypeFolderWorkspaceEditorElement } from './member-type-folder-editor.element.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import {
UmbEntityNamedDetailWorkspaceContextBase,
type UmbRoutableWorkspaceContext,
type UmbSubmittableWorkspaceContext,
} from '@umbraco-cms/backoffice/workspace';
import type { IRoutingInfo, PageComponent } from '@umbraco-cms/backoffice/router';
import type { UmbFolderModel } from '@umbraco-cms/backoffice/tree';
export class UmbMemberTypeFolderWorkspaceContext
extends UmbEntityNamedDetailWorkspaceContextBase<UmbFolderModel, UmbMemberTypeFolderRepository>
implements UmbSubmittableWorkspaceContext, UmbRoutableWorkspaceContext
{
constructor(host: UmbControllerHost) {
super(host, {
workspaceAlias: UMB_MEMBER_TYPE_FOLDER_WORKSPACE_ALIAS,
entityType: UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
detailRepositoryAlias: UMB_MEMBER_TYPE_FOLDER_REPOSITORY_ALIAS,
});
this.routes.setRoutes([
{
path: 'edit/:unique',
component: UmbMemberTypeFolderWorkspaceEditorElement,
setup: (component: PageComponent, info: IRoutingInfo) => {
const unique = info.match.params.unique;
this.load(unique);
},
},
]);
}
}
export { UmbMemberTypeFolderWorkspaceContext as api };

View File

@@ -0,0 +1,14 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js';
import type { UmbMemberTypeFolderWorkspaceContext } from './member-type-folder-workspace.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
export const UMB_MEMBER_TYPE_FOLDER_WORKSPACE_CONTEXT = new UmbContextToken<
UmbWorkspaceContext,
UmbMemberTypeFolderWorkspaceContext
>(
'UmbWorkspaceContext',
undefined,
(context): context is UmbMemberTypeFolderWorkspaceContext =>
context.getEntityType?.() === UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
);

View File

@@ -0,0 +1,14 @@
import { UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js';
import { UmbPathPattern } from '@umbraco-cms/backoffice/router';
import { UMB_SETTINGS_SECTION_PATHNAME } from '@umbraco-cms/backoffice/settings';
import { UMB_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/workspace';
export const UMB_MEMBER_TYPE_FOLDER_WORKSPACE_PATH = UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({
sectionName: UMB_SETTINGS_SECTION_PATHNAME,
entityType: UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
});
export const UMB_EDIT_MEMBER_TYPE_FOLDER_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>(
'edit/:unique',
UMB_MEMBER_TYPE_FOLDER_WORKSPACE_PATH,
);

View File

@@ -1,13 +1,11 @@
import { UMB_MEMBER_TYPE_ROOT_WORKSPACE_ALIAS } from '../constants.js';
import { UMB_MEMBER_TYPE_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../entity.js';
import {
UMB_MEMBER_TYPE_TREE_ALIAS,
UMB_MEMBER_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS,
UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS,
UMB_MEMBER_TYPE_TREE_STORE_ALIAS,
} from './constants.js';
import { manifests as treeItemChildrenManifest } from './tree-item-children/manifests.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { manifests as folderManifests } from './folder/manifests.js';
import { manifests as treeItemChildrenManifests } from './tree-item-children/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
{
@@ -38,23 +36,6 @@ export const manifests: Array<UmbExtensionManifest> = [
name: 'Member Type Tree Item',
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_ENTITY_TYPE],
},
{
type: 'workspaceView',
kind: 'collection',
alias: 'Umb.WorkspaceView.MemberType.TreeItemChildrenCollection',
name: 'Member Type Tree Item Children Collection Workspace View',
meta: {
label: '#general_design',
pathname: 'design',
icon: 'icon-member-dashed-line',
collectionAlias: UMB_MEMBER_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
oneOf: [UMB_MEMBER_TYPE_ROOT_WORKSPACE_ALIAS],
},
],
},
...treeItemChildrenManifest,
...folderManifests,
...treeItemChildrenManifests,
];

View File

@@ -1,5 +1,5 @@
import { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../entity.js';
import { UmbMemberTypeTreeServerDataSource } from './member-type-tree.server.data-source.js';
import { UmbMemberTypeTreeServerDataSource } from './server-data-source/member-type-tree.server.data-source.js';
import type { UmbMemberTypeTreeItemModel, UmbMemberTypeTreeRootModel } from './types.js';
import { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT } from './member-type-tree.store.context-token.js';
import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree';

View File

@@ -1,62 +0,0 @@
import { UMB_MEMBER_TYPE_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../entity.js';
import type { UmbMemberTypeTreeItemModel } from './types.js';
import type { UmbTreeChildrenOfRequestArgs, UmbTreeRootItemsRequestArgs } from '@umbraco-cms/backoffice/tree';
import { UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { MemberTypeTreeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
* A data source for the MemberType tree that fetches data from the server
* @class UmbMemberTypeTreeServerDataSource
* @implements {UmbTreeDataSource}
*/
export class UmbMemberTypeTreeServerDataSource extends UmbTreeServerDataSourceBase<
MemberTypeTreeItemResponseModel,
UmbMemberTypeTreeItemModel
> {
/**
* Creates an instance of UmbMemberTypeTreeServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMemberTypeTreeServerDataSource
*/
constructor(host: UmbControllerHost) {
super(host, {
getRootItems,
getChildrenOf,
getAncestorsOf,
mapper,
});
}
}
const getRootItems = (args: UmbTreeRootItemsRequestArgs) =>
// eslint-disable-next-line local-rules/no-direct-api-import
MemberTypeService.getTreeMemberTypeRoot({ query: { skip: args.skip, take: args.take } });
const getChildrenOf = (args: UmbTreeChildrenOfRequestArgs) => {
if (args.parent.unique === null) {
return getRootItems(args);
} else {
throw new Error('Not supported for the member type tree');
}
};
const getAncestorsOf = () => {
throw new Error('Not supported for the member type tree');
};
const mapper = (item: MemberTypeTreeItemResponseModel): UmbMemberTypeTreeItemModel => {
return {
unique: item.id,
parent: {
unique: item.parent ? item.parent.id : null,
entityType: item.parent ? UMB_MEMBER_TYPE_ENTITY_TYPE : UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE,
},
name: item.name,
entityType: UMB_MEMBER_TYPE_ENTITY_TYPE,
hasChildren: item.hasChildren,
isFolder: false,
icon: item.icon,
};
};

View File

@@ -0,0 +1,75 @@
import {
UMB_MEMBER_TYPE_ENTITY_TYPE,
UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE,
UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE,
} from '../../entity.js';
import { UmbManagementApiMemberTypeTreeDataRequestManager } from './member-type-tree.server.request-manager.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { MemberTypeTreeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
import type {
UmbTreeAncestorsOfRequestArgs,
UmbTreeChildrenOfRequestArgs,
UmbTreeDataSource,
UmbTreeRootItemsRequestArgs,
} from '@umbraco-cms/backoffice/tree';
import type { UmbMemberTypeTreeItemModel } from '../types.js';
/**
* A data source for the Member Type tree that fetches data from the server
* @class UmbMemberTypeTreeServerDataSource
*/
export class UmbMemberTypeTreeServerDataSource
extends UmbControllerBase
implements UmbTreeDataSource<UmbMemberTypeTreeItemModel>
{
#treeRequestManager = new UmbManagementApiMemberTypeTreeDataRequestManager(this);
async getRootItems(args: UmbTreeRootItemsRequestArgs) {
const { data, error } = await this.#treeRequestManager.getRootItems(args);
const mappedData = data
? {
...data,
items: data?.items.map((item) => this.#mapItem(item)),
}
: undefined;
return { data: mappedData, error };
}
async getChildrenOf(args: UmbTreeChildrenOfRequestArgs) {
const { data, error } = await this.#treeRequestManager.getChildrenOf(args);
const mappedData = data
? {
...data,
items: data?.items.map((item) => this.#mapItem(item)),
}
: undefined;
return { data: mappedData, error };
}
async getAncestorsOf(args: UmbTreeAncestorsOfRequestArgs) {
const { data, error } = await this.#treeRequestManager.getAncestorsOf(args);
const mappedData = data?.map((item) => this.#mapItem(item));
return { data: mappedData, error };
}
#mapItem(item: MemberTypeTreeItemResponseModel): UmbMemberTypeTreeItemModel {
return {
unique: item.id,
parent: {
unique: item.parent ? item.parent.id : null,
entityType: item.parent ? UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE : UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE,
},
name: item.name,
entityType: item.isFolder ? UMB_MEMBER_TYPE_FOLDER_ENTITY_TYPE : UMB_MEMBER_TYPE_ENTITY_TYPE,
hasChildren: item.hasChildren,
isFolder: item.isFolder,
icon: item.icon,
};
}
}

View File

@@ -0,0 +1,68 @@
/* eslint-disable local-rules/no-direct-api-import */
import { MemberTypeService } from '@umbraco-cms/backoffice/external/backend-api';
import { UmbManagementApiTreeDataRequestManager } from '@umbraco-cms/backoffice/management-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type {
MemberTypeTreeItemResponseModel,
PagedMemberTypeTreeItemResponseModel,
SubsetMemberTypeTreeItemResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import type {
UmbManagementApiTreeAncestorsOfRequestArgs,
UmbManagementApiTreeChildrenOfRequestArgs,
UmbManagementApiTreeRootItemsRequestArgs,
UmbManagementApiTreeSiblingsFromRequestArgs,
} from '@umbraco-cms/backoffice/management-api';
export class UmbManagementApiMemberTypeTreeDataRequestManager extends UmbManagementApiTreeDataRequestManager<
MemberTypeTreeItemResponseModel,
UmbManagementApiTreeRootItemsRequestArgs,
PagedMemberTypeTreeItemResponseModel,
UmbManagementApiTreeChildrenOfRequestArgs,
PagedMemberTypeTreeItemResponseModel,
UmbManagementApiTreeAncestorsOfRequestArgs,
Array<MemberTypeTreeItemResponseModel>,
UmbManagementApiTreeSiblingsFromRequestArgs,
SubsetMemberTypeTreeItemResponseModel
> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: (args) =>
MemberTypeService.getTreeMemberTypeRoot({
query: {
foldersOnly: args.foldersOnly,
skip: args.paging.skip,
take: args.paging.take,
},
}),
getChildrenOf: (args) =>
MemberTypeService.getTreeMemberTypeChildren({
query: {
parentId: args.parent.unique,
foldersOnly: args.foldersOnly,
skip: args.paging.skip,
take: args.paging.take,
},
}),
getAncestorsOf: (args) =>
MemberTypeService.getTreeMemberTypeAncestors({
query: {
descendantId: args.treeItem.unique,
},
}),
getSiblingsFrom: (args) =>
MemberTypeService.getTreeMemberTypeSiblings({
query: {
foldersOnly: args.foldersOnly,
target: args.paging.target.unique,
before: args.paging.takeBefore,
after: args.paging.takeAfter,
},
}),
});
}
}

Some files were not shown because too many files have changed in this diff Show More