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.") .WithDetail("The import failed due to not being able to convert the file into proper xml.")
.Build()), .Build()),
MediaTypeImportOperationStatus.MediaTypeExists => BadRequest(problemDetailsBuilder 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.") .WithDetail("The import failed because the media type that was being imported already exits.")
.Build()), .Build()),
MediaTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder MediaTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
@@ -42,6 +42,6 @@ public abstract class MediaTypeControllerBase : ManagementApiControllerBase
.WithTitle("Invalid Id") .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.") .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()), .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.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.MemberType;
using Umbraco.Cms.Core; using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services;
@@ -24,9 +25,12 @@ public class CopyMemberTypeController : MemberTypeControllerBase
[ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [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 return result.Success
? CreatedAtId<ByKeyMemberTypeController>(controller => nameof(controller.ByKey), result.Result!.Key) ? 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 Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Controllers.DocumentType; using Umbraco.Cms.Api.Management.Controllers.DocumentType;
using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Api.Management.Routing;
@@ -18,4 +19,29 @@ public abstract class MemberTypeControllerBase : ManagementApiControllerBase
protected IActionResult StructureOperationStatusResult(ContentTypeStructureOperationStatus status) protected IActionResult StructureOperationStatusResult(ContentTypeStructureOperationStatus status)
=> DocumentTypeControllerBase.ContentTypeStructureOperationStatusResult(status, "member"); => 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}")] [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.MemberType}")]
[ApiExplorerSettings(GroupName = "Member Type")] [ApiExplorerSettings(GroupName = "Member Type")]
[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)] [Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)]
public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<MemberTypeTreeItemResponseModel> public class MemberTypeTreeControllerBase : FolderTreeControllerBase<MemberTypeTreeItemResponseModel>
{ {
private readonly IMemberTypeService _memberTypeService; private readonly IMemberTypeService _memberTypeService;
@@ -37,6 +37,8 @@ public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase<Member
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType; protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.MemberType;
protected override UmbracoObjectTypes FolderObjectType => UmbracoObjectTypes.MemberTypeContainer;
protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities) protected override MemberTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid? parentKey, IEntitySlim[] entities)
{ {
var memberTypes = _memberTypeService var memberTypes = _memberTypeService

View File

@@ -32,5 +32,8 @@ public class RootMemberTypeTreeController : MemberTypeTreeControllerBase
int skip = 0, int skip = 0,
int take = 100, int take = 100,
bool foldersOnly = false) 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.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Api.Management.Services.Flags; using Umbraco.Cms.Api.Management.Services.Flags;
using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Cms.Api.Management.ViewModels.Tree;
@@ -9,10 +10,14 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree;
public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase
{ {
public SiblingMemberTypeTreeController( [Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
IEntityService entityService, public SiblingMemberTypeTreeController(IEntityService entityService, IMemberTypeService memberTypeService)
FlagProviderCollection flagProviders, : base(entityService, memberTypeService)
IMemberTypeService memberTypeService) {
}
[ActivatorUtilitiesConstructor]
public SiblingMemberTypeTreeController(IEntityService entityService, FlagProviderCollection flagProviders, IMemberTypeService memberTypeService)
: base(entityService, flagProviders, memberTypeService) : base(entityService, flagProviders, memberTypeService)
{ {
} }
@@ -25,5 +30,8 @@ public class SiblingMemberTypeTreeController : MemberTypeTreeControllerBase
int before, int before,
int after, int after,
bool foldersOnly = false) 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(IContentType contentType);
FileContentResult Create(IMediaType mediaType); 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;
using Umbraco.Cms.Core.Models.ContentTypeEditing; using Umbraco.Cms.Core.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services;
@@ -24,6 +24,7 @@ internal sealed class MemberTypeEditingPresentationFactory : ContentTypeEditingP
createModel.Key = requestModel.Id; createModel.Key = requestModel.Id;
createModel.Compositions = MapCompositions(requestModel.Compositions); createModel.Compositions = MapCompositions(requestModel.Compositions);
createModel.ContainerKey = requestModel.Parent?.Id;
MapPropertyTypeSensitivityAndVisibility(createModel.Properties, requestModel.Properties); MapPropertyTypeSensitivityAndVisibility(createModel.Properties, requestModel.Properties);

View File

@@ -27,6 +27,12 @@ public class UdtFileContentFactory : IUdtFileContentFactory
return XmlTofile(mediaType, xml); 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) => private static FileContentResult XmlTofile(IContentTypeBase contentTypeBase, XElement xml) =>
new(Encoding.UTF8.GetBytes(xml.ToDataString()), MediaTypeNames.Application.Octet) 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": { "/umbraco/management/api/v1/imaging/resize/urls": {
"get": { "get": {
"tags": [ "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": { "responses": {
"201": { "201": {
"description": "Created", "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": { "/umbraco/management/api/v1/member-type/available-compositions": {
"post": { "post": {
"tags": [ "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": { "/umbraco/management/api/v1/tree/member-type/root": {
"get": { "get": {
"tags": [ "tags": [
@@ -20712,6 +21442,14 @@
"format": "int32", "format": "int32",
"default": 100 "default": 100
} }
},
{
"name": "foldersOnly",
"in": "query",
"schema": {
"type": "boolean",
"default": false
}
} }
], ],
"responses": { "responses": {
@@ -20773,6 +21511,14 @@
"type": "integer", "type": "integer",
"format": "int32" "format": "int32"
} }
},
{
"name": "foldersOnly",
"in": "query",
"schema": {
"type": "boolean",
"default": false
}
} }
], ],
"responses": { "responses": {
@@ -36266,6 +37012,20 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"CopyMemberTypeRequestModel": {
"type": "object",
"properties": {
"target": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
}
},
"additionalProperties": false
},
"CreateDataTypeRequestModel": { "CreateDataTypeRequestModel": {
"required": [ "required": [
"editorAlias", "editorAlias",
@@ -37361,6 +38121,14 @@
"format": "uuid", "format": "uuid",
"nullable": true "nullable": true
}, },
"parent": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
},
"compositions": { "compositions": {
"type": "array", "type": "array",
"items": { "items": {
@@ -40574,7 +41342,7 @@
}, },
"actionParameters": { "actionParameters": {
"type": "object", "type": "object",
"additionalProperties": {}, "additionalProperties": { },
"nullable": true "nullable": true
} }
}, },
@@ -40824,6 +41592,22 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"ImportMemberTypeRequestModel": {
"required": [
"file"
],
"type": "object",
"properties": {
"file": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
]
}
},
"additionalProperties": false
},
"IndexResponseModel": { "IndexResponseModel": {
"required": [ "required": [
"canRebuild", "canRebuild",
@@ -41199,7 +41983,7 @@
}, },
"extensions": { "extensions": {
"type": "array", "type": "array",
"items": {} "items": { }
} }
}, },
"additionalProperties": false "additionalProperties": false
@@ -42852,6 +43636,7 @@
"hasChildren", "hasChildren",
"icon", "icon",
"id", "id",
"isFolder",
"name" "name"
], ],
"type": "object", "type": "object",
@@ -42884,6 +43669,9 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"isFolder": {
"type": "boolean"
},
"icon": { "icon": {
"type": "string" "type": "string"
} }
@@ -43129,6 +43917,20 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"MoveMemberTypeRequestModel": {
"type": "object",
"properties": {
"target": {
"oneOf": [
{
"$ref": "#/components/schemas/ReferenceByIdModel"
}
],
"nullable": true
}
},
"additionalProperties": false
},
"NamedEntityTreeItemResponseModel": { "NamedEntityTreeItemResponseModel": {
"required": [ "required": [
"flags", "flags",
@@ -45071,7 +45873,7 @@
"nullable": true "nullable": true
} }
}, },
"additionalProperties": {} "additionalProperties": { }
}, },
"ProblemDetailsBuilderModel": { "ProblemDetailsBuilderModel": {
"type": "object", "type": "object",
@@ -48718,8 +49520,7 @@
"type": "string" "type": "string"
}, },
"subscribeToNewsletter": { "subscribeToNewsletter": {
"type": "boolean", "type": "boolean"
"readOnly": true
} }
}, },
"additionalProperties": false "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; namespace Umbraco.Cms.Api.Management.ViewModels.MemberType;
public class CreateMemberTypeRequestModel public class CreateMemberTypeRequestModel
: CreateContentTypeRequestModelBase<CreateMemberTypePropertyTypeRequestModel, CreateMemberTypePropertyTypeContainerRequestModel> : CreateContentTypeWithParentRequestModelBase<CreateMemberTypePropertyTypeRequestModel, CreateMemberTypePropertyTypeContainerRequestModel>
{ {
public IEnumerable<MemberTypeComposition> Compositions { get; set; } = Enumerable.Empty<MemberTypeComposition>(); 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; public string Icon { get; set; } = string.Empty;
} }

View File

@@ -430,6 +430,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>(); Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>();
Services.AddUnique<IContentTypeImportService, ContentTypeImportService>(); Services.AddUnique<IContentTypeImportService, ContentTypeImportService>();
Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>(); Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>();
Services.AddUnique<IMemberTypeImportService, MemberTypeImportService>();
// add validation services // add validation services
Services.AddUnique<IElementSwitchValidator, ElementSwitchValidator>(); 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 class MemberTypeCreateModel : MemberTypeModelBase
{ {
public Guid? Key { get; set; } 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.Models.ContentTypeEditing;
using Umbraco.Cms.Core.Services.OperationStatus; 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) 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) if (result.Success is false)
{ {
return result; return result;
@@ -80,7 +80,7 @@ internal sealed class MemberTypeEditingService : ContentTypeEditingServiceBase<I
protected override UmbracoObjectTypes ContentTypeObjectType => UmbracoObjectTypes.MemberType; 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(); 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) public Attempt<OperationResult<MoveOperationStatusType>?> Move(TItem moving, int containerId)
{ {
EventMessages eventMessages = EventMessagesFactory.Get(); 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>>(); var moveInfo = new List<MoveEventInfo<TItem>>();

View File

@@ -374,7 +374,11 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
return xml; 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( var info = new XElement(
"Info", "Info",
@@ -410,7 +414,7 @@ internal sealed class EntityXmlSerializer : IEntityXmlSerializer
SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
var xml = new XElement( var xml = new XElement(
IEntityXmlSerializer.MediaTypeElementName, elementName,
info, info,
structure, structure,
genericProperties, genericProperties,

View File

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

View File

@@ -10,13 +10,21 @@ public interface IPackageDataInstallation
InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId); InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId);
/// <summary> /// <summary>
/// Imports and saves package xml as <see cref="IContentType"/> /// Imports and saves package xml as <see cref="IMediaType"/>.
/// </summary> /// </summary>
/// <param name="docTypeElements">Xml to import</param> /// <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="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); 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>( IReadOnlyList<TContentBase> ImportContentBase<TContentBase, TContentTypeComposition>(
IEnumerable<CompiledPackageContentBase> docs, IEnumerable<CompiledPackageContentBase> docs,
IDictionary<string, TContentTypeComposition> importedDocumentTypes, 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), => Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.DocumentType),
IEntityXmlSerializer.MediaTypeElementName IEntityXmlSerializer.MediaTypeElementName
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MediaType), => Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MediaType),
IEntityXmlSerializer.MemberTypeElementName
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MemberType),
_ => Attempt<UmbracoEntityTypes>.Fail() _ => 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 IEntityService _entityService;
private readonly IContentTypeService _contentTypeService; private readonly IContentTypeService _contentTypeService;
private readonly IContentService _contentService; private readonly IContentService _contentService;
private readonly IMemberTypeService _memberTypeService;
public PackageDataInstallation( public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory, IDataValueEditorFactory dataValueEditorFactory,
@@ -58,7 +59,8 @@ namespace Umbraco.Cms.Infrastructure.Packaging
IMediaService mediaService, IMediaService mediaService,
IMediaTypeService mediaTypeService, IMediaTypeService mediaTypeService,
ITemplateContentParserService templateContentParserService, ITemplateContentParserService templateContentParserService,
ITemplateService templateService) ITemplateService templateService,
IMemberTypeService memberTypeService)
{ {
_dataValueEditorFactory = dataValueEditorFactory; _dataValueEditorFactory = dataValueEditorFactory;
_logger = logger; _logger = logger;
@@ -76,10 +78,48 @@ namespace Umbraco.Cms.Infrastructure.Packaging
_mediaTypeService = mediaTypeService; _mediaTypeService = mediaTypeService;
_templateContentParserService = templateContentParserService; _templateContentParserService = templateContentParserService;
_templateService = templateService; _templateService = templateService;
_memberTypeService = memberTypeService;
} }
// Also remove factory service registration when this constructor is removed [Obsolete("Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.")]
[Obsolete("Use the constructor with Infrastructure.IScopeProvider and without global settings and hosting environment instead.")] 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( public PackageDataInstallation(
IDataValueEditorFactory dataValueEditorFactory, IDataValueEditorFactory dataValueEditorFactory,
ILogger<PackageDataInstallation> logger, ILogger<PackageDataInstallation> logger,
@@ -107,7 +147,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
contentTypeService, contentTypeService,
contentService, contentService,
propertyEditors, propertyEditors,
(Umbraco.Cms.Infrastructure.Scoping.IScopeProvider)scopeProvider, (IScopeProvider)scopeProvider,
shortStringHelper, shortStringHelper,
serializer, serializer,
mediaService, mediaService,
@@ -163,27 +203,27 @@ namespace Umbraco.Cms.Infrastructure.Packaging
} }
} }
/// <summary> /// <inheritdoc/>
/// 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>
public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId) public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId)
#pragma warning disable CS0618 // Type or member is obsolete
=> ImportMediaTypes(docTypeElements, userId, out _); => ImportMediaTypes(docTypeElements, userId, out _);
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary> [Obsolete("This method is not used in Umbraco outside of this class so will be made private in Umbraco 19.")]
/// 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>
public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId, public IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId,
out IEnumerable<EntityContainer> entityContainersInstalled) out IEnumerable<EntityContainer> entityContainersInstalled)
=> ImportDocumentTypes(docTypeElements.ToList(), true, userId, _mediaTypeService, => ImportDocumentTypes(docTypeElements.ToList(), true, userId, _mediaTypeService,
out entityContainersInstalled); 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 #endregion
#region Content #region Content
@@ -805,26 +845,23 @@ namespace Umbraco.Cms.Infrastructure.Packaging
{ {
if (typeof(T) == typeof(IContentType)) if (typeof(T) == typeof(IContentType))
{ {
if (parent is null) return parent is null
{ ? new ContentType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T
return new ContentType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T; : new ContentType(_shortStringHelper, (IContentType)parent, alias) { Key = key } as T;
}
else
{
return new ContentType(_shortStringHelper, (IContentType)parent, alias) { Key = key } as T;
}
} }
if (typeof(T) == typeof(IMediaType)) if (typeof(T) == typeof(IMediaType))
{ {
if (parent is null) return parent is null
{ ? new MediaType(_shortStringHelper, parentId) { Alias = alias, Key = key } as T
return 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"); 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; target?: ReferenceByIdModel | null;
}; };
export type CopyMemberTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type CreateDataTypeRequestModel = { export type CreateDataTypeRequestModel = {
name: string; name: string;
editorAlias: string; editorAlias: string;
@@ -327,6 +331,7 @@ export type CreateMemberTypeRequestModel = {
properties: Array<CreateMemberTypePropertyTypeRequestModel>; properties: Array<CreateMemberTypePropertyTypeRequestModel>;
containers: Array<CreateMemberTypePropertyTypeContainerRequestModel>; containers: Array<CreateMemberTypePropertyTypeContainerRequestModel>;
id?: string | null; id?: string | null;
parent?: ReferenceByIdModel | null;
compositions: Array<MemberTypeCompositionModel>; compositions: Array<MemberTypeCompositionModel>;
}; };
@@ -1110,6 +1115,10 @@ export type ImportMediaTypeRequestModel = {
file: ReferenceByIdModel; file: ReferenceByIdModel;
}; };
export type ImportMemberTypeRequestModel = {
file: ReferenceByIdModel;
};
export type IndexResponseModel = { export type IndexResponseModel = {
name: string; name: string;
healthStatus: HealthStatusResponseModel; healthStatus: HealthStatusResponseModel;
@@ -1566,6 +1575,7 @@ export type MemberTypeTreeItemResponseModel = {
parent?: ReferenceByIdModel | null; parent?: ReferenceByIdModel | null;
flags: Array<FlagModel>; flags: Array<FlagModel>;
name: string; name: string;
isFolder: boolean;
icon: string; icon: string;
}; };
@@ -1636,6 +1646,10 @@ export type MoveMediaTypeRequestModel = {
target?: ReferenceByIdModel | null; target?: ReferenceByIdModel | null;
}; };
export type MoveMemberTypeRequestModel = {
target?: ReferenceByIdModel | null;
};
export type NamedEntityTreeItemResponseModel = { export type NamedEntityTreeItemResponseModel = {
hasChildren: boolean; hasChildren: boolean;
id: string; id: string;
@@ -2923,7 +2937,7 @@ export type UserInstallRequestModel = {
name: string; name: string;
email: string; email: string;
password: string; password: string;
readonly subscribeToNewsletter: boolean; subscribeToNewsletter: boolean;
}; };
export type UserItemResponseModel = { export type UserItemResponseModel = {
@@ -3158,12 +3172,6 @@ export type UpgradeSettingsResponseModelWritable = {
oldVersion: string; oldVersion: string;
}; };
export type UserInstallRequestModelWritable = {
name: string;
email: string;
password: string;
};
export type GetCultureData = { export type GetCultureData = {
body?: never; body?: never;
path?: never; path?: never;
@@ -10718,7 +10726,7 @@ export type GetMemberTypeByIdCompositionReferencesResponses = {
export type GetMemberTypeByIdCompositionReferencesResponse = GetMemberTypeByIdCompositionReferencesResponses[keyof GetMemberTypeByIdCompositionReferencesResponses]; export type GetMemberTypeByIdCompositionReferencesResponse = GetMemberTypeByIdCompositionReferencesResponses[keyof GetMemberTypeByIdCompositionReferencesResponses];
export type PostMemberTypeByIdCopyData = { export type PostMemberTypeByIdCopyData = {
body?: never; body?: CopyMemberTypeRequestModel;
path: { path: {
id: string; id: string;
}; };
@@ -10754,6 +10762,115 @@ export type PostMemberTypeByIdCopyResponses = {
201: unknown; 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 = { export type PostMemberTypeAvailableCompositionsData = {
body?: MemberTypeCompositionRequestModel; body?: MemberTypeCompositionRequestModel;
path?: never; path?: never;
@@ -10952,12 +11069,109 @@ export type PutMemberTypeFolderByIdResponses = {
200: unknown; 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 = { export type GetTreeMemberTypeRootData = {
body?: never; body?: never;
path?: never; path?: never;
query?: { query?: {
skip?: number; skip?: number;
take?: number; take?: number;
foldersOnly?: boolean;
}; };
url: '/umbraco/management/api/v1/tree/member-type/root'; url: '/umbraco/management/api/v1/tree/member-type/root';
}; };
@@ -10989,6 +11203,7 @@ export type GetTreeMemberTypeSiblingsData = {
target?: string; target?: string;
before?: number; before?: number;
after?: number; after?: number;
foldersOnly?: boolean;
}; };
url: '/umbraco/management/api/v1/tree/member-type/siblings'; 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> { export class UmbDefaultMediaTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() { override async getHref() {
const parentEntityType = this.args.entityType as UmbMediaTypeRootEntityType | UmbMediaTypeFolderEntityType; 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; const parentUnique = this.args.unique ?? null;

View File

@@ -8,4 +8,8 @@ export * from './search/constants.js';
export * from './tree/constants.js'; export * from './tree/constants.js';
export * from './workspace/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 './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 { UMB_CREATE_MEMBER_TYPE_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action'; import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action';
import type { MetaEntityCreateOptionAction } 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> { export class UmbDefaultMemberTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() { 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'); if (!parentEntityType) throw new Error('Entity type is required to create a member type');
const parentUnique = this.args.unique ?? null; 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'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -8,10 +8,12 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
name: 'Default Member Type Entity Create Option Action', name: 'Default Member Type Entity Create Option Action',
weight: 1000, weight: 1000,
api: () => import('./default-member-type-create-option-action.js'), 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: { meta: {
icon: 'icon-user', icon: 'icon-user',
label: '#content_membertype', 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 defaultManifests } from './default/manifests.js';
import { manifests as folderManifests } from './folder/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -8,7 +13,9 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
kind: 'create', kind: 'create',
alias: 'Umb.EntityAction.MemberType.Create', alias: 'Umb.EntityAction.MemberType.Create',
name: 'Create Member Type Entity Action', 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, ...defaultManifests,
...folderManifests,
]; ];

View File

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

View File

@@ -1,13 +1,13 @@
import { UmbDuplicateMemberTypeServerDataSource } from './member-type-duplicate.server.data-source.js'; import { UmbDuplicateMemberTypeServerDataSource } from './member-type-duplicate.server.data-source.js';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; 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'; 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); #duplicateSource = new UmbDuplicateMemberTypeServerDataSource(this);
async requestDuplicate(args: UmbDuplicateRequestArgs) { async requestDuplicateTo(args: UmbDuplicateToRequestArgs) {
const { error } = await this.#duplicateSource.duplicate(args); const { error } = await this.#duplicateSource.duplicateTo(args);
if (!error) { if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);

View File

@@ -1,13 +1,13 @@
import { MemberTypeService } 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'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources'; 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 * @class UmbDuplicateMemberTypeServerDataSource
*/ */
export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateDataSource { export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateToDataSource {
#host: UmbControllerHost; #host: UmbControllerHost;
/** /**
@@ -20,18 +20,22 @@ export class UmbDuplicateMemberTypeServerDataSource implements UmbDuplicateDataS
} }
/** /**
* Duplicate an item for the given unique * Duplicate an item for the given unique to the destination unique
* @param {UmbDuplicateRequestArgs} args * @param {UmbDuplicateToRequestArgs} args
* @returns {*} * @returns {*}
* @memberof UmbDuplicateDataTypeServerDataSource * @memberof UmbDuplicateMemberTypeServerDataSource
*/ */
async duplicate(args: UmbDuplicateRequestArgs) { async duplicateTo(args: UmbDuplicateToRequestArgs) {
if (!args.unique) throw new Error('Unique is missing'); if (!args.unique) throw new Error('Unique is missing');
if (args.destination.unique === undefined) throw new Error('Destination unique is missing');
return tryExecute( return tryExecute(
this.#host, this.#host,
MemberTypeService.postMemberTypeByIdCopy({ MemberTypeService.postMemberTypeByIdCopy({
path: { id: args.unique }, 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 {
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../entity.js'; 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 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 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'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
@@ -17,5 +23,8 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
}, },
}, },
...createManifests, ...createManifests,
...moveManifests,
...duplicateManifests, ...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_ENTITY_TYPE = 'member-type';
export const UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE = 'member-type-root'; 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 UmbMemberTypeEntityType = typeof UMB_MEMBER_TYPE_ENTITY_TYPE;
export type UmbMemberTypeRootEntityType = typeof UMB_MEMBER_TYPE_ROOT_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 {*} * @returns {*}
* @memberof UmbMemberTypeDetailServerDataSource * @memberof UmbMemberTypeDetailServerDataSource
*/ */
async create(model: UmbMemberTypeDetailModel) { async create(model: UmbMemberTypeDetailModel, parentUnique: string | null = null) {
if (!model) throw new Error('Member Type is missing'); if (!model) throw new Error('Member Type is missing');
// TODO: make data mapper to prevent errors // TODO: make data mapper to prevent errors
@@ -162,6 +162,7 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailDataSource<
}), }),
containers: model.containers, containers: model.containers,
id: model.unique, id: model.unique,
parent: parentUnique ? { id: parentUnique } : null,
compositions: model.compositions.map((composition) => { compositions: model.compositions.map((composition) => {
return { return {
memberType: { id: composition.contentType.unique }, 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 { export interface UmbMemberTypeItemModel {
entityType: UmbMemberTypeEntityType; entityType: UmbMemberTypeEntityType | UmbMemberTypeFolderEntityType;
unique: string; unique: string;
name: string; name: string;
icon: 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 { 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'; 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_ENTITY_TYPE, UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../entity.js';
import { import {
UMB_MEMBER_TYPE_TREE_ALIAS, UMB_MEMBER_TYPE_TREE_ALIAS,
UMB_MEMBER_TYPE_TREE_ITEM_CHILDREN_COLLECTION_ALIAS,
UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_TREE_REPOSITORY_ALIAS,
UMB_MEMBER_TYPE_TREE_STORE_ALIAS, UMB_MEMBER_TYPE_TREE_STORE_ALIAS,
} from './constants.js'; } from './constants.js';
import { manifests as treeItemChildrenManifest } from './tree-item-children/manifests.js'; import { manifests as folderManifests } from './folder/manifests.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { manifests as treeItemChildrenManifests } from './tree-item-children/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [ export const manifests: Array<UmbExtensionManifest> = [
{ {
@@ -38,23 +36,6 @@ export const manifests: Array<UmbExtensionManifest> = [
name: 'Member Type Tree Item', name: 'Member Type Tree Item',
forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_ENTITY_TYPE], forEntityTypes: [UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE, UMB_MEMBER_TYPE_ENTITY_TYPE],
}, },
{ ...folderManifests,
type: 'workspaceView', ...treeItemChildrenManifests,
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,
]; ];

View File

@@ -1,5 +1,5 @@
import { UMB_MEMBER_TYPE_ROOT_ENTITY_TYPE } from '../entity.js'; 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 type { UmbMemberTypeTreeItemModel, UmbMemberTypeTreeRootModel } from './types.js';
import { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT } from './member-type-tree.store.context-token.js'; import { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT } from './member-type-tree.store.context-token.js';
import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree'; 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