[V14] import/export media/document type endpoints (#16100)
* Working import/export media/document types * WIP * Refactoring of import doctype/media types - added analyze endpoint to extract relevant data without fully processing the file - split up import endpoints into POST & PUT - removed availableAtAction as the new endpoint allows clients to call the POST/PUT endpoints with confidence - Added a new service that is responsible for turning temp files into Import compatible XML and being able to extracty partial information from it * Wrap persistance access in scopes * Typos, formatting, clean-up * PR feedback * update openapi spec * Changed deleteFile flag to _temporaryFileService.EnlistDeleteIfScopeCompletes * Itty bitty typo * Moved magic cleanup into its own method so orchestration can decide when. --------- Co-authored-by: Sven Geusens <sge@umbraco.dk> Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
@@ -108,6 +109,31 @@ public abstract class DocumentTypeControllerBase : ManagementApiControllerBase
|
||||
_ => new ObjectResult("Unknown content type structure operation status") { StatusCode = StatusCodes.Status500InternalServerError }
|
||||
});
|
||||
|
||||
protected IActionResult ContentTypeImportOperationStatusResult(ContentTypeImportOperationStatus operationStatus) =>
|
||||
OperationStatusResult(operationStatus, problemDetailsBuilder => operationStatus switch
|
||||
{
|
||||
ContentTypeImportOperationStatus.TemporaryFileNotFound => NotFound(problemDetailsBuilder
|
||||
.WithTitle("Temporary file not found")
|
||||
.Build()),
|
||||
ContentTypeImportOperationStatus.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()),
|
||||
ContentTypeImportOperationStatus.DocumentTypeExists => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Failed to import because document type exits")
|
||||
.WithDetail("The import failed because the document type that was being imported already exits")
|
||||
.Build()),
|
||||
ContentTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Type Mismatch")
|
||||
.WithDetail("The import failed because the file contained an entity that is not a content type.")
|
||||
.Build()),
|
||||
ContentTypeImportOperationStatus.IdMismatch => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid Id")
|
||||
.WithDetail("The import failed because the id of the document type you are trying to update did not match the id in the file.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown document type import operation status.")
|
||||
});
|
||||
|
||||
protected IActionResult ContentEditingOperationStatusResult(ContentEditingOperationStatus status) =>
|
||||
OperationStatusResult(status, problemDetailsBuilder => status switch
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Asp.Versioning;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ExportDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IUdtFileContentFactory _fileContentFactory;
|
||||
|
||||
public ExportDocumentTypeController(
|
||||
IContentTypeService contentTypeService,
|
||||
IUdtFileContentFactory fileContentFactory)
|
||||
{
|
||||
_contentTypeService = contentTypeService;
|
||||
_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)
|
||||
{
|
||||
IContentType? contentType = _contentTypeService.Get(id);
|
||||
if (contentType is null)
|
||||
{
|
||||
return OperationStatusResult(ContentTypeOperationStatus.NotFound);
|
||||
}
|
||||
|
||||
return _fileContentFactory.Create(contentType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ImportExistingDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IContentTypeImportService _contentTypeImportService;
|
||||
|
||||
public ImportExistingDocumentTypeController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IContentTypeImportService contentTypeImportService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_contentTypeImportService = contentTypeImportService;
|
||||
}
|
||||
|
||||
[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,
|
||||
ImportDocumentTypeRequestModel model)
|
||||
{
|
||||
Attempt<IContentType?, ContentTypeImportOperationStatus> importAttempt = await _contentTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor), id);
|
||||
|
||||
return importAttempt.Success is false
|
||||
? ContentTypeImportOperationStatusResult(importAttempt.Status)
|
||||
: Ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ImportNewDocumentTypeController : DocumentTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IContentTypeImportService _contentTypeImportService;
|
||||
|
||||
public ImportNewDocumentTypeController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IContentTypeImportService contentTypeImportService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_contentTypeImportService = contentTypeImportService;
|
||||
}
|
||||
|
||||
[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,
|
||||
ImportDocumentTypeRequestModel model)
|
||||
{
|
||||
|
||||
Attempt<IContentType?, ContentTypeImportOperationStatus> importAttempt = await _contentTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return importAttempt.Success is false
|
||||
? ContentTypeImportOperationStatusResult(importAttempt.Status)
|
||||
: CreatedAtId<ByKeyDocumentTypeController>(
|
||||
controller => nameof(controller.ByKey),
|
||||
importAttempt.Result!.Key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Import;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Import;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class AnalyzeImportController : ImportControllerBase
|
||||
{
|
||||
private readonly ITemporaryFileToXmlImportService _temporaryFileToXmlImportService;
|
||||
private readonly IUmbracoMapper _mapper;
|
||||
|
||||
public AnalyzeImportController(
|
||||
ITemporaryFileToXmlImportService temporaryFileToXmlImportService,
|
||||
IUmbracoMapper mapper)
|
||||
{
|
||||
_temporaryFileToXmlImportService = temporaryFileToXmlImportService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("analyze")]
|
||||
[ProducesResponseType(typeof(EntityImportAnalysisResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Analyze(CancellationToken cancellationToken, Guid temporaryFileId)
|
||||
{
|
||||
Attempt<EntityXmlAnalysis?, TemporaryFileXmlImportOperationStatus> analyzeResult = await _temporaryFileToXmlImportService.AnalyzeAsync(temporaryFileId);
|
||||
|
||||
return analyzeResult.Success is false
|
||||
? TemporaryFileXmlImportOperationStatusResult(analyzeResult.Status)
|
||||
: Ok(_mapper.Map<EntityImportAnalysisResponseModel>(analyzeResult.Result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Import;
|
||||
|
||||
[VersionedApiBackOfficeRoute("import")]
|
||||
[ApiExplorerSettings(GroupName = "Import")]
|
||||
public abstract class ImportControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected static IActionResult TemporaryFileXmlImportOperationStatusResult(TemporaryFileXmlImportOperationStatus operationStatus) =>
|
||||
OperationStatusResult(operationStatus, problemDetailsBuilder => operationStatus switch
|
||||
{
|
||||
TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound => new NotFoundObjectResult(problemDetailsBuilder
|
||||
.WithTitle("Temporary file not found")
|
||||
.Build()),
|
||||
TemporaryFileXmlImportOperationStatus.UndeterminedEntityType => new BadRequestObjectResult(problemDetailsBuilder
|
||||
.WithTitle("Unable to determine entity type")
|
||||
.Build()),
|
||||
_ => new ObjectResult("Unknown temporary file import operation status") { StatusCode = StatusCodes.Status500InternalServerError },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Asp.Versioning;
|
||||
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;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ExportMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IMediaTypeService _mediaTypeService;
|
||||
private readonly IUdtFileContentFactory _fileContentFactory;
|
||||
|
||||
public ExportMediaTypeController(
|
||||
IMediaTypeService mediaTypeService,
|
||||
IUdtFileContentFactory fileContentFactory)
|
||||
{
|
||||
_mediaTypeService = mediaTypeService;
|
||||
_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)
|
||||
{
|
||||
IMediaType? mediaType = _mediaTypeService.Get(id);
|
||||
if (mediaType is null)
|
||||
{
|
||||
return OperationStatusResult(ContentTypeOperationStatus.NotFound);
|
||||
}
|
||||
|
||||
return _fileContentFactory.Create(mediaType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ImportExistingMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IMediaTypeImportService _mediaTypeImportService;
|
||||
|
||||
public ImportExistingMediaTypeController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMediaTypeImportService mediaTypeImportService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_mediaTypeImportService = mediaTypeImportService;
|
||||
}
|
||||
|
||||
[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,
|
||||
ImportMediaTypeRequestModel model)
|
||||
{
|
||||
Attempt<IMediaType?, MediaTypeImportOperationStatus> importAttempt = await _mediaTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return importAttempt.Success is false
|
||||
? MediaTypeImportOperationStatusResult(importAttempt.Status)
|
||||
: Ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.MediaType;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
public class ImportNewMediaTypeController : MediaTypeControllerBase
|
||||
{
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
private readonly IMediaTypeImportService _mediaTypeImportService;
|
||||
|
||||
public ImportNewMediaTypeController(
|
||||
IBackOfficeSecurityAccessor backOfficeSecurityAccessor,
|
||||
IMediaTypeImportService mediaTypeImportService)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
_mediaTypeImportService = mediaTypeImportService;
|
||||
}
|
||||
|
||||
[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,
|
||||
ImportMediaTypeRequestModel model)
|
||||
{
|
||||
Attempt<IMediaType?, MediaTypeImportOperationStatus> importAttempt = await _mediaTypeImportService.Import(model.File.Id, CurrentUserKey(_backOfficeSecurityAccessor));
|
||||
|
||||
return importAttempt.Success is false
|
||||
? MediaTypeImportOperationStatusResult(importAttempt.Status)
|
||||
: CreatedAtId<ByKeyMediaTypeController>(controller => nameof(controller.ByKey), importAttempt.Result!.Key);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.Controllers.DocumentType;
|
||||
using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
@@ -18,4 +20,29 @@ public abstract class MediaTypeControllerBase : ManagementApiControllerBase
|
||||
|
||||
protected IActionResult StructureOperationStatusResult(ContentTypeStructureOperationStatus status)
|
||||
=> DocumentTypeControllerBase.ContentTypeStructureOperationStatusResult(status, "media");
|
||||
|
||||
protected IActionResult MediaTypeImportOperationStatusResult(MediaTypeImportOperationStatus operationStatus) =>
|
||||
OperationStatusResult(operationStatus, problemDetailsBuilder => operationStatus switch
|
||||
{
|
||||
MediaTypeImportOperationStatus.TemporaryFileNotFound => NotFound(problemDetailsBuilder
|
||||
.WithTitle("Temporary file not found")
|
||||
.Build()),
|
||||
MediaTypeImportOperationStatus.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()),
|
||||
MediaTypeImportOperationStatus.MediaTypeExists => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Failed to import because media type exits")
|
||||
.WithDetail("The import failed because the media type that was being imported already exits.")
|
||||
.Build()),
|
||||
MediaTypeImportOperationStatus.TypeMismatch => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Type Mismatch")
|
||||
.WithDetail("The import failed because the file contained an entity that is not a media type.")
|
||||
.Build()),
|
||||
MediaTypeImportOperationStatus.IdMismatch => BadRequest(problemDetailsBuilder
|
||||
.WithTitle("Invalid Id")
|
||||
.WithDetail("The import failed because the id of the media type you are trying to update did not match the id in the file.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown media type import operation status.")
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class ExportBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddExport(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.Services.AddTransient<IUdtFileContentFactory, UdtFileContentFactory>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Umbraco.Cms.Api.Management.Mapping.Import;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.DependencyInjection;
|
||||
|
||||
internal static class ImportBuilderExtensions
|
||||
{
|
||||
internal static IUmbracoBuilder AddImport(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<EntityImportAnalysisViewModelsMapDefinition>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,9 @@ public static partial class UmbracoBuilderExtensions
|
||||
.AddPasswordConfiguration()
|
||||
.AddSupplemenataryLocalizedTextFileSources()
|
||||
.AddUserData()
|
||||
.AddSegment();
|
||||
.AddSegment()
|
||||
.AddExport()
|
||||
.AddImport();
|
||||
|
||||
services
|
||||
.ConfigureOptions<ConfigureApiBehaviorOptions>()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public interface IUdtFileContentFactory
|
||||
{
|
||||
FileContentResult Create(IContentType contentType);
|
||||
|
||||
FileContentResult Create(IMediaType mediaType);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Factories;
|
||||
|
||||
public class UdtFileContentFactory : IUdtFileContentFactory
|
||||
{
|
||||
private readonly IEntityXmlSerializer _entityXmlSerializer;
|
||||
|
||||
public UdtFileContentFactory(IEntityXmlSerializer entityXmlSerializer)
|
||||
=> _entityXmlSerializer = entityXmlSerializer;
|
||||
|
||||
public FileContentResult Create(IContentType contentType)
|
||||
{
|
||||
XElement xml = _entityXmlSerializer.Serialize(contentType);
|
||||
return XmlTofile(contentType, xml);
|
||||
}
|
||||
|
||||
public FileContentResult Create(IMediaType mediaType)
|
||||
{
|
||||
XElement xml = _entityXmlSerializer.Serialize(mediaType);
|
||||
return XmlTofile(mediaType, xml);
|
||||
}
|
||||
|
||||
private static FileContentResult XmlTofile(IContentTypeBase contentTypeBase, XElement xml) =>
|
||||
new(Encoding.UTF8.GetBytes(xml.ToDataString()), MediaTypeNames.Application.Octet)
|
||||
{
|
||||
FileDownloadName = $"{contentTypeBase.Alias}.udt"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Import;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models.ImportExport;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Import;
|
||||
|
||||
public class EntityImportAnalysisViewModelsMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<EntityXmlAnalysis, EntityImportAnalysisResponseModel>((_, _) => new EntityImportAnalysisResponseModel(), Map);
|
||||
}
|
||||
|
||||
private void Map(EntityXmlAnalysis source, EntityImportAnalysisResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Key = source.Key;
|
||||
target.Alias = source.Alias;
|
||||
target.EntityType = source.EntityType.ToString();
|
||||
}
|
||||
}
|
||||
@@ -5057,6 +5057,209 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-type/{id}/export": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document Type"
|
||||
],
|
||||
"operationId": "GetDocumentTypeByIdExport",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"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 do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-type/{id}/import": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"Document Type"
|
||||
],
|
||||
"operationId": "PutDocumentTypeByIdImport",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"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 do 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/document-type/{id}/move": {
|
||||
"put": {
|
||||
"tags": [
|
||||
@@ -5837,6 +6040,152 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-type/import": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Document Type"
|
||||
],
|
||||
"operationId": "PostDocumentTypeImport",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportDocumentTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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 do 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/item/document-type": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -10893,6 +11242,76 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/import/analyze": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Import"
|
||||
],
|
||||
"operationId": "GetImportAnalyze",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "temporaryFileId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EntityImportAnalysisResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/indexer": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -13601,6 +14020,209 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/media-type/{id}/export": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Media Type"
|
||||
],
|
||||
"operationId": "GetMediaTypeByIdExport",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"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 do not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/media-type/{id}/import": {
|
||||
"put": {
|
||||
"tags": [
|
||||
"Media Type"
|
||||
],
|
||||
"operationId": "PutMediaTypeByIdImport",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"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 do 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/media-type/{id}/move": {
|
||||
"put": {
|
||||
"tags": [
|
||||
@@ -14346,6 +14968,152 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/media-type/import": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Media Type"
|
||||
],
|
||||
"operationId": "PostMediaTypeImport",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ImportMediaTypeRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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 do 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/media-type/ancestors": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -36517,6 +37285,27 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EntityImportAnalysisResponseModel": {
|
||||
"required": [
|
||||
"entityType"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"entityType": {
|
||||
"type": "string"
|
||||
},
|
||||
"alias": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventMessageTypeModel": {
|
||||
"enum": [
|
||||
"Default",
|
||||
@@ -36863,6 +37652,38 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ImportDocumentTypeRequestModel": {
|
||||
"required": [
|
||||
"file"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ReferenceByIdModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ImportMediaTypeRequestModel": {
|
||||
"required": [
|
||||
"file"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ReferenceByIdModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"IndexResponseModel": {
|
||||
"required": [
|
||||
"canRebuild",
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Umbraco.Cms.Api.Management.Services;
|
||||
internal sealed class DictionaryItemImportService : IDictionaryItemImportService
|
||||
{
|
||||
private readonly IDictionaryItemService _dictionaryItemService;
|
||||
private readonly PackageDataInstallation _packageDataInstallation;
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly ILogger<DictionaryItemImportService> _logger;
|
||||
private readonly ITemporaryFileService _temporaryFileService;
|
||||
private readonly IUserService _userService;
|
||||
@@ -25,7 +25,7 @@ internal sealed class DictionaryItemImportService : IDictionaryItemImportService
|
||||
|
||||
public DictionaryItemImportService(
|
||||
IDictionaryItemService dictionaryItemService,
|
||||
PackageDataInstallation packageDataInstallation,
|
||||
IPackageDataInstallation packageDataInstallation,
|
||||
ILogger<DictionaryItemImportService> logger,
|
||||
ITemporaryFileService temporaryFileService,
|
||||
IUserService userService,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType;
|
||||
|
||||
public class ImportDocumentTypeRequestModel
|
||||
{
|
||||
public required ReferenceByIdModel File { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Import;
|
||||
|
||||
public class EntityImportAnalysisResponseModel
|
||||
{
|
||||
public string EntityType { get; set; } = string.Empty;
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
public Guid? Key { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.MediaType;
|
||||
|
||||
public class ImportMediaTypeRequestModel
|
||||
{
|
||||
public required ReferenceByIdModel File { get; set; }
|
||||
}
|
||||
@@ -37,6 +37,7 @@ using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Core.DynamicRoot;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services.FileSystem;
|
||||
using Umbraco.Cms.Core.Services.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.Querying.RecycleBin;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Core.Telemetry;
|
||||
@@ -395,6 +396,11 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
|
||||
// Segments
|
||||
Services.AddUnique<ISegmentService, NoopSegmentService>();
|
||||
|
||||
// definition Import/export
|
||||
Services.AddUnique<ITemporaryFileToXmlImportService, TemporaryFileToXmlImportService>();
|
||||
Services.AddUnique<IContentTypeImportService, ContentTypeImportService>();
|
||||
Services.AddUnique<IMediaTypeImportService, MediaTypeImportService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/Umbraco.Core/Models/ImportExport/EntityXmlAnalysis.cs
Normal file
12
src/Umbraco.Core/Models/ImportExport/EntityXmlAnalysis.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.ImportExport;
|
||||
|
||||
public class EntityXmlAnalysis
|
||||
{
|
||||
public UmbracoEntityTypes EntityType { get; set; }
|
||||
|
||||
public string? Alias { get; set; }
|
||||
|
||||
public Guid? Key { get; set; }
|
||||
}
|
||||
@@ -396,7 +396,7 @@ internal class EntityXmlSerializer : IEntityXmlSerializer
|
||||
{
|
||||
foreach (ContentTypeSort allowedType in mediaType.AllowedContentTypes)
|
||||
{
|
||||
structure.Add(new XElement("MediaType", allowedType.Alias));
|
||||
structure.Add(new XElement(IEntityXmlSerializer.MediaTypeElementName, allowedType.Alias));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ internal class EntityXmlSerializer : IEntityXmlSerializer
|
||||
SerializePropertyGroups(mediaType.PropertyGroups)); // TODO Rename to PropertyGroups
|
||||
|
||||
var xml = new XElement(
|
||||
"MediaType",
|
||||
IEntityXmlSerializer.MediaTypeElementName,
|
||||
info,
|
||||
structure,
|
||||
genericProperties,
|
||||
@@ -474,7 +474,7 @@ internal class EntityXmlSerializer : IEntityXmlSerializer
|
||||
{
|
||||
foreach (ContentTypeSort allowedType in contentType.AllowedContentTypes)
|
||||
{
|
||||
structure.Add(new XElement("DocumentType", allowedType.Alias));
|
||||
structure.Add(new XElement(IEntityXmlSerializer.DocumentTypeElementName, allowedType.Alias));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ internal class EntityXmlSerializer : IEntityXmlSerializer
|
||||
SerializePropertyGroups(contentType.PropertyGroups)); // TODO Rename to PropertyGroups
|
||||
|
||||
var xml = new XElement(
|
||||
"DocumentType",
|
||||
IEntityXmlSerializer.DocumentTypeElementName,
|
||||
info,
|
||||
structure,
|
||||
genericProperties,
|
||||
|
||||
@@ -8,6 +8,9 @@ namespace Umbraco.Cms.Core.Services;
|
||||
/// </summary>
|
||||
public interface IEntityXmlSerializer
|
||||
{
|
||||
internal const string DocumentTypeElementName = "DocumentType";
|
||||
internal const string MediaTypeElementName = "MediaType";
|
||||
|
||||
/// <summary>
|
||||
/// Exports an IContent item as an XElement.
|
||||
/// </summary>
|
||||
|
||||
80
src/Umbraco.Core/Services/IPackageDataInstallation.cs
Normal file
80
src/Umbraco.Core/Services/IPackageDataInstallation.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Packaging;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface IPackageDataInstallation
|
||||
{
|
||||
InstallationSummary InstallPackageData(CompiledPackage compiledPackage, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="IContentType"/>
|
||||
/// </summary>
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
IReadOnlyList<IMediaType> ImportMediaTypes(IEnumerable<XElement> docTypeElements, int userId);
|
||||
|
||||
IReadOnlyList<TContentBase> ImportContentBase<TContentBase, TContentTypeComposition>(
|
||||
IEnumerable<CompiledPackageContentBase> docs,
|
||||
IDictionary<string, TContentTypeComposition> importedDocumentTypes,
|
||||
int userId,
|
||||
IContentTypeBaseService<TContentTypeComposition> typeService,
|
||||
IContentServiceBase<TContentBase> service)
|
||||
where TContentBase : class, IContentBase
|
||||
where TContentTypeComposition : IContentTypeComposition;
|
||||
|
||||
IReadOnlyList<IContentType> ImportDocumentType(XElement docTypeElement, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="IContentType"/>
|
||||
/// </summary>
|
||||
/// <param name="docTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation. Default is zero (admin).</param>
|
||||
/// <returns>An enumerable list of generated ContentTypes</returns>
|
||||
IReadOnlyList<IContentType> ImportDocumentTypes(IEnumerable<XElement> docTypeElements, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="IDataType"/>
|
||||
/// </summary>
|
||||
/// <param name="dataTypeElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the user</param>
|
||||
/// <returns>An enumerable list of generated DataTypeDefinitions</returns>
|
||||
IReadOnlyList<IDataType> ImportDataTypes(IReadOnlyCollection<XElement> dataTypeElements, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves the 'DictionaryItems' part of the package xml as a list of <see cref="IDictionaryItem"/>
|
||||
/// </summary>
|
||||
/// <param name="dictionaryItemElementList">Xml to import</param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns>An enumerable list of dictionary items</returns>
|
||||
IReadOnlyList<IDictionaryItem> ImportDictionaryItems(IEnumerable<XElement> dictionaryItemElementList,
|
||||
int userId);
|
||||
|
||||
IEnumerable<IDictionaryItem> ImportDictionaryItem(XElement dictionaryItemElement, int userId, Guid? parentId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves the 'Languages' part of a package xml as a list of <see cref="ILanguage"/>
|
||||
/// </summary>
|
||||
/// <param name="languageElements">Xml to import</param>
|
||||
/// <param name="userId">Optional id of the User performing the operation</param>
|
||||
/// <returns>An enumerable list of generated languages</returns>
|
||||
IReadOnlyList<ILanguage> ImportLanguages(IEnumerable<XElement> languageElements, int userId);
|
||||
|
||||
IEnumerable<ITemplate> ImportTemplate(XElement templateElement, int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Imports and saves package xml as <see cref="ITemplate"/>
|
||||
/// </summary>
|
||||
/// <param name="templateElements">Xml to import</param>
|
||||
/// <param name="userId">Optional user id</param>
|
||||
/// <returns>An enumerable list of generated Templates</returns>
|
||||
IReadOnlyList<ITemplate> ImportTemplates(IReadOnlyCollection<XElement> templateElements, int userId);
|
||||
|
||||
Guid GetContentTypeKey(XElement contentType);
|
||||
|
||||
string? GetEntityTypeAlias(XElement entityType);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
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 ContentTypeImportService : IContentTypeImportService
|
||||
{
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly ITemporaryFileToXmlImportService _temporaryFileToXmlImportService;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IUserIdKeyResolver _userIdKeyResolver;
|
||||
|
||||
public ContentTypeImportService(
|
||||
IPackageDataInstallation packageDataInstallation,
|
||||
IEntityService entityService,
|
||||
ITemporaryFileToXmlImportService temporaryFileToXmlImportService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IUserIdKeyResolver userIdKeyResolver)
|
||||
{
|
||||
_packageDataInstallation = packageDataInstallation;
|
||||
_entityService = entityService;
|
||||
_temporaryFileToXmlImportService = temporaryFileToXmlImportService;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
_userIdKeyResolver = userIdKeyResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports the contentType
|
||||
/// </summary>
|
||||
/// <param name="temporaryFileId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="contentTypeId">the id of the contentType to overwrite, null if a new contentType should be created</param>
|
||||
/// <returns></returns>
|
||||
public async Task<Attempt<IContentType?, ContentTypeImportOperationStatus>> Import(
|
||||
Guid temporaryFileId,
|
||||
Guid userKey,
|
||||
Guid? contentTypeId = 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<IContentType?, ContentTypeImportOperationStatus>(
|
||||
loadXmlAttempt.Status is TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound
|
||||
? ContentTypeImportOperationStatus.TemporaryFileNotFound
|
||||
: ContentTypeImportOperationStatus.TemporaryFileConversionFailure,
|
||||
null);
|
||||
}
|
||||
|
||||
Attempt<UmbracoEntityTypes> packageEntityTypeAttempt = _temporaryFileToXmlImportService.GetEntityType(loadXmlAttempt.Result!);
|
||||
if (packageEntityTypeAttempt.Success is false ||
|
||||
packageEntityTypeAttempt.Result is not UmbracoEntityTypes.DocumentType)
|
||||
{
|
||||
return Attempt.FailWithStatus<IContentType?, ContentTypeImportOperationStatus>(
|
||||
ContentTypeImportOperationStatus.TypeMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
Guid packageEntityKey = _packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!);
|
||||
if (contentTypeId is not null && contentTypeId.Equals(packageEntityKey) is false)
|
||||
{
|
||||
return Attempt.FailWithStatus<IContentType?, ContentTypeImportOperationStatus>(
|
||||
ContentTypeImportOperationStatus.IdMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
var entityExits = _entityService.Exists(packageEntityKey, UmbracoObjectTypes.DocumentType);
|
||||
if (entityExits && contentTypeId is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<IContentType?, ContentTypeImportOperationStatus>(
|
||||
ContentTypeImportOperationStatus.DocumentTypeExists,
|
||||
null);
|
||||
}
|
||||
|
||||
IReadOnlyList<IContentType> importResult =
|
||||
_packageDataInstallation.ImportDocumentType(loadXmlAttempt.Result!, await _userIdKeyResolver.GetAsync(userKey));
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return Attempt.SucceedWithStatus<IContentType?, ContentTypeImportOperationStatus>(
|
||||
entityExits
|
||||
? ContentTypeImportOperationStatus.SuccessUpdated
|
||||
: ContentTypeImportOperationStatus.SuccessCreated,
|
||||
importResult[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public interface IContentTypeImportService
|
||||
{
|
||||
Task<Attempt<IContentType?, ContentTypeImportOperationStatus>> Import(Guid temporaryFileId, Guid userKey, Guid? contentTypeId = null);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public interface IMediaTypeImportService
|
||||
{
|
||||
Task<Attempt<IMediaType?, MediaTypeImportOperationStatus>> Import(
|
||||
Guid temporaryFileId,
|
||||
Guid userKey,
|
||||
Guid? mediaTypeId = null);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.ImportExport;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public interface ITemporaryFileToXmlImportService
|
||||
{
|
||||
Task<Attempt<XElement?, TemporaryFileXmlImportOperationStatus>> LoadXElementFromTemporaryFileAsync(
|
||||
Guid temporaryFileId);
|
||||
|
||||
Attempt<UmbracoEntityTypes> GetEntityType(XElement entityElement);
|
||||
|
||||
Task<Attempt<EntityXmlAnalysis?, TemporaryFileXmlImportOperationStatus>> AnalyzeAsync(
|
||||
Guid temporaryFileId);
|
||||
|
||||
void CleanupFileIfScopeCompletes(Guid temporaryFileId);
|
||||
}
|
||||
@@ -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 MediaTypeImportService : IMediaTypeImportService
|
||||
{
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly IEntityService _entityService;
|
||||
private readonly ITemporaryFileToXmlImportService _temporaryFileToXmlImportService;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
private readonly IUserIdKeyResolver _userIdKeyResolver;
|
||||
|
||||
public MediaTypeImportService(
|
||||
IPackageDataInstallation packageDataInstallation,
|
||||
IEntityService entityService,
|
||||
ITemporaryFileToXmlImportService temporaryFileToXmlImportService,
|
||||
ICoreScopeProvider coreScopeProvider,
|
||||
IUserIdKeyResolver userIdKeyResolver)
|
||||
{
|
||||
_packageDataInstallation = packageDataInstallation;
|
||||
_entityService = entityService;
|
||||
_temporaryFileToXmlImportService = temporaryFileToXmlImportService;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
_userIdKeyResolver = userIdKeyResolver;
|
||||
}
|
||||
|
||||
public async Task<Attempt<IMediaType?, MediaTypeImportOperationStatus>> Import(
|
||||
Guid temporaryFileId,
|
||||
Guid userKey,
|
||||
Guid? mediaTypeId = 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<IMediaType?, MediaTypeImportOperationStatus>(
|
||||
loadXmlAttempt.Status is TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound
|
||||
? MediaTypeImportOperationStatus.TemporaryFileNotFound
|
||||
: MediaTypeImportOperationStatus.TemporaryFileConversionFailure,
|
||||
null);
|
||||
}
|
||||
|
||||
Attempt<UmbracoEntityTypes> packageEntityTypeAttempt = _temporaryFileToXmlImportService.GetEntityType(loadXmlAttempt.Result!);
|
||||
if (packageEntityTypeAttempt.Success is false ||
|
||||
packageEntityTypeAttempt.Result is not UmbracoEntityTypes.MediaType)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMediaType?, MediaTypeImportOperationStatus>(
|
||||
MediaTypeImportOperationStatus.TypeMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
Guid packageEntityKey = _packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!);
|
||||
if (mediaTypeId is not null && mediaTypeId.Equals(packageEntityKey) is false)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMediaType?, MediaTypeImportOperationStatus>(
|
||||
MediaTypeImportOperationStatus.IdMismatch,
|
||||
null);
|
||||
}
|
||||
|
||||
var entityExits = _entityService.Exists(
|
||||
_packageDataInstallation.GetContentTypeKey(loadXmlAttempt.Result!),
|
||||
UmbracoObjectTypes.MediaType);
|
||||
if (entityExits && mediaTypeId is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<IMediaType?, MediaTypeImportOperationStatus>(
|
||||
MediaTypeImportOperationStatus.MediaTypeExists,
|
||||
null);
|
||||
}
|
||||
|
||||
IReadOnlyList<IMediaType> importResult =
|
||||
_packageDataInstallation.ImportMediaTypes(new[] { loadXmlAttempt.Result! }, await _userIdKeyResolver.GetAsync(userKey));
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return Attempt.SucceedWithStatus<IMediaType?, MediaTypeImportOperationStatus>(
|
||||
entityExits
|
||||
? MediaTypeImportOperationStatus.SuccessUpdated
|
||||
: MediaTypeImportOperationStatus.SuccessCreated,
|
||||
importResult[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.ImportExport;
|
||||
using Umbraco.Cms.Core.Models.TemporaryFile;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.ImportExport;
|
||||
|
||||
public class TemporaryFileToXmlImportService : ITemporaryFileToXmlImportService
|
||||
{
|
||||
private readonly ITemporaryFileService _temporaryFileService;
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly ICoreScopeProvider _coreScopeProvider;
|
||||
|
||||
public TemporaryFileToXmlImportService(
|
||||
ITemporaryFileService temporaryFileService,
|
||||
IPackageDataInstallation packageDataInstallation,
|
||||
ICoreScopeProvider coreScopeProvider)
|
||||
{
|
||||
_temporaryFileService = temporaryFileService;
|
||||
_packageDataInstallation = packageDataInstallation;
|
||||
_coreScopeProvider = coreScopeProvider;
|
||||
}
|
||||
|
||||
/// <remark>
|
||||
/// Only if this method is called within a scope, the temporary file will be cleaned up if that scope completes.
|
||||
/// </remark>
|
||||
public async Task<Attempt<XElement?, TemporaryFileXmlImportOperationStatus>> LoadXElementFromTemporaryFileAsync(
|
||||
Guid temporaryFileId)
|
||||
{
|
||||
TemporaryFileModel? documentTypeFile = await _temporaryFileService.GetAsync(temporaryFileId);
|
||||
if (documentTypeFile is null)
|
||||
{
|
||||
return Attempt.FailWithStatus<XElement?, TemporaryFileXmlImportOperationStatus>(
|
||||
TemporaryFileXmlImportOperationStatus.TemporaryFileNotFound, null);
|
||||
}
|
||||
|
||||
XDocument document;
|
||||
await using (Stream fileStream = documentTypeFile.OpenReadStream())
|
||||
{
|
||||
document = await XDocument.LoadAsync(fileStream, LoadOptions.None, CancellationToken.None);
|
||||
}
|
||||
|
||||
return Attempt.SucceedWithStatus<XElement?, TemporaryFileXmlImportOperationStatus>(
|
||||
TemporaryFileXmlImportOperationStatus.Success,
|
||||
document.Root);
|
||||
}
|
||||
|
||||
public void CleanupFileIfScopeCompletes(Guid temporaryFileId)
|
||||
=> _temporaryFileService.EnlistDeleteIfScopeCompletes(temporaryFileId, _coreScopeProvider);
|
||||
|
||||
public Attempt<UmbracoEntityTypes> GetEntityType(XElement entityElement)
|
||||
{
|
||||
var entityType = entityElement.Name.ToString();
|
||||
return entityType switch
|
||||
{
|
||||
IEntityXmlSerializer.DocumentTypeElementName
|
||||
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.DocumentType),
|
||||
IEntityXmlSerializer.MediaTypeElementName
|
||||
=> Attempt<UmbracoEntityTypes>.Succeed(UmbracoEntityTypes.MediaType),
|
||||
_ => Attempt<UmbracoEntityTypes>.Fail()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the file trough the use of <see cref="LoadXElementFromTemporaryFileAsync"/> and returns basic information regarding the entity that would be imported if this file was processed by
|
||||
/// <see cref="IContentTypeImportService.Import(Guid,Guid,System.Nullable{System.Guid})"/> or <see cref="IMediaTypeImportService.Import(Guid,Guid,System.Nullable{System.Guid})"/>.
|
||||
/// </summary>
|
||||
/// <remarks>As this method does not persist anything, no scope is created and the temporary file is not cleaned up, see remark in <see cref="LoadXElementFromTemporaryFileAsync"/>.</remarks>
|
||||
public async Task<Attempt<EntityXmlAnalysis?, TemporaryFileXmlImportOperationStatus>> AnalyzeAsync(
|
||||
Guid temporaryFileId)
|
||||
{
|
||||
Attempt<XElement?, TemporaryFileXmlImportOperationStatus> xmlElementAttempt =
|
||||
await LoadXElementFromTemporaryFileAsync(temporaryFileId);
|
||||
|
||||
if (xmlElementAttempt.Success is false)
|
||||
{
|
||||
return Attempt<EntityXmlAnalysis, TemporaryFileXmlImportOperationStatus>.Fail(xmlElementAttempt.Status);
|
||||
}
|
||||
|
||||
Attempt<UmbracoEntityTypes> entityTypeAttempt = GetEntityType(xmlElementAttempt.Result!);
|
||||
if (entityTypeAttempt.Success is false)
|
||||
{
|
||||
return Attempt<EntityXmlAnalysis, TemporaryFileXmlImportOperationStatus>.Fail(
|
||||
TemporaryFileXmlImportOperationStatus.UndeterminedEntityType);
|
||||
}
|
||||
|
||||
Guid entityTypeKey = _packageDataInstallation.GetContentTypeKey(xmlElementAttempt.Result!);
|
||||
var entityTypeAlias = _packageDataInstallation.GetEntityTypeAlias(xmlElementAttempt.Result!);
|
||||
return Attempt<EntityXmlAnalysis?, TemporaryFileXmlImportOperationStatus>.Succeed(
|
||||
TemporaryFileXmlImportOperationStatus.Success,
|
||||
new EntityXmlAnalysis
|
||||
{
|
||||
EntityType = entityTypeAttempt.Result, Alias = entityTypeAlias, Key = entityTypeKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum ContentTypeImportOperationStatus
|
||||
{
|
||||
SuccessCreated,
|
||||
SuccessUpdated,
|
||||
TemporaryFileNotFound,
|
||||
TemporaryFileConversionFailure,
|
||||
DocumentTypeExists,
|
||||
IdMismatch,
|
||||
TypeMismatch,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum MediaTypeImportOperationStatus
|
||||
{
|
||||
SuccessCreated,
|
||||
SuccessUpdated,
|
||||
TemporaryFileNotFound,
|
||||
TemporaryFileConversionFailure,
|
||||
MediaTypeExists,
|
||||
TypeMismatch,
|
||||
IdMismatch
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum TemporaryFileXmlImportOperationStatus
|
||||
{
|
||||
Success = 0,
|
||||
TemporaryFileNotFound,
|
||||
UndeterminedEntityType,
|
||||
}
|
||||
@@ -98,8 +98,8 @@ public static partial class UmbracoBuilderExtensions
|
||||
packageRepoFileName);
|
||||
|
||||
// Factory registration is only required because of ambiguous constructor
|
||||
private static PackageDataInstallation CreatePackageDataInstallation(IServiceProvider factory)
|
||||
=> new(
|
||||
private static IPackageDataInstallation CreatePackageDataInstallation(IServiceProvider factory)
|
||||
=> new PackageDataInstallation(
|
||||
factory.GetRequiredService<IDataValueEditorFactory>(),
|
||||
factory.GetRequiredService<ILogger<PackageDataInstallation>>(),
|
||||
factory.GetRequiredService<IFileService>(),
|
||||
|
||||
@@ -21,7 +21,7 @@ using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
{
|
||||
public class PackageDataInstallation
|
||||
public class PackageDataInstallation : IPackageDataInstallation
|
||||
{
|
||||
private readonly IDataValueEditorFactory _dataValueEditorFactory;
|
||||
private readonly ILogger<PackageDataInstallation> _logger;
|
||||
@@ -590,7 +590,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
//Iterate the sorted document types and create them as IContentType objects
|
||||
foreach (XElement documentType in documentTypes)
|
||||
{
|
||||
var alias = documentType.Element("Info")?.Element("Alias")?.Value;
|
||||
var alias = GetEntityTypeAlias(documentType);
|
||||
|
||||
if (alias is not null && importedContentTypes.ContainsKey(alias) == false)
|
||||
{
|
||||
@@ -623,7 +623,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
//Update the structure here - we can't do it until all DocTypes have been created
|
||||
foreach (XElement documentType in documentTypes)
|
||||
{
|
||||
var alias = documentType.Element("Info")?.Element("Alias")?.Value;
|
||||
var alias = GetEntityTypeAlias(documentType);
|
||||
XElement? structureElement = documentType.Element("Structure");
|
||||
//Ensure that we only update ContentTypes which has actual structure-elements
|
||||
if (structureElement == null || structureElement.Elements().Any() == false || alias is null)
|
||||
@@ -661,7 +661,7 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
// exist which contains it's folders
|
||||
&& ((string?)infoElement.Element("Master")).IsNullOrWhiteSpace())
|
||||
{
|
||||
var alias = documentType.Element("Info")?.Element("Alias")?.Value;
|
||||
var alias = GetEntityTypeAlias(documentType);
|
||||
var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash);
|
||||
|
||||
XAttribute? folderKeysAttribute = documentType.Attribute("FolderKeys");
|
||||
@@ -744,13 +744,19 @@ namespace Umbraco.Cms.Infrastructure.Packaging
|
||||
return _contentTypeService.GetContainer(tryCreateFolder.Result!.Entity!.Id);
|
||||
}
|
||||
|
||||
public Guid GetContentTypeKey(XElement contentType)
|
||||
=> Guid.Parse(contentType.Element("Info")!.Element("Key")!.Value);
|
||||
|
||||
public string? GetEntityTypeAlias(XElement entityType)
|
||||
=> entityType.Element("Info")?.Element("Alias")?.Value;
|
||||
|
||||
private T CreateContentTypeFromXml<T>(
|
||||
XElement documentType,
|
||||
IReadOnlyDictionary<string, T> importedContentTypes,
|
||||
IContentTypeBaseService<T> service)
|
||||
where T : class, IContentTypeComposition
|
||||
{
|
||||
var key = Guid.Parse(documentType.Element("Info")!.Element("Key")!.Value);
|
||||
var key = GetContentTypeKey(documentType);
|
||||
|
||||
XElement infoElement = documentType.Element("Info")!;
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@ using System.Xml.Linq;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Packaging;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Packaging;
|
||||
|
||||
public class PackageInstallation : IPackageInstallation
|
||||
{
|
||||
private readonly PackageDataInstallation _packageDataInstallation;
|
||||
private readonly IPackageDataInstallation _packageDataInstallation;
|
||||
private readonly CompiledPackageXmlParser _parser;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PackageInstallation" /> class.
|
||||
/// </summary>
|
||||
public PackageInstallation(PackageDataInstallation packageDataInstallation, CompiledPackageXmlParser parser)
|
||||
public PackageInstallation(IPackageDataInstallation packageDataInstallation, CompiledPackageXmlParser parser)
|
||||
{
|
||||
_packageDataInstallation =
|
||||
packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation));
|
||||
|
||||
@@ -73,7 +73,7 @@ public class PackageDataInstallationTests : UmbracoIntegrationTestWithContent
|
||||
//// Builder.ComposeFileSystems();
|
||||
//// }
|
||||
|
||||
private PackageDataInstallation PackageDataInstallation => GetRequiredService<PackageDataInstallation>();
|
||||
private IPackageDataInstallation PackageDataInstallation => GetRequiredService<IPackageDataInstallation>();
|
||||
|
||||
private IMediaService MediaService => GetRequiredService<IMediaService>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user