diff --git a/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs index 25de601698..48e0b9cf8a 100644 --- a/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Content/ContentControllerBase.cs @@ -32,6 +32,10 @@ public class ContentControllerBase : ManagementApiControllerBase .WithDetail("The selected template was not allowed for the operation.") .Build()), ContentEditingOperationStatus.PropertyTypeNotFound => NotFound("One or more property types could not be found"), + ContentEditingOperationStatus.InTrash => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Content is in the recycle bin") + .WithDetail("Could not perform the operation because the targeted content was in the recycle bin.") + .Build()), ContentEditingOperationStatus.Unknown => StatusCode(StatusCodes.Status500InternalServerError, "Unknown error. Please see the log for more details."), _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown content operation status.") }; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs index bebaa1fb4f..86f0d103b0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs @@ -27,7 +27,7 @@ public class ByKeyDocumentController : DocumentControllerBase IContent? content = await _contentEditingService.GetAsync(key); if (content == null) { - return NotFound(); + return DocumentNotFound(); } DocumentResponseModel model = await _documentPresentationFactory.CreateResponseModelAsync(content); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs index 73e4c192e6..fa3574041d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs @@ -11,4 +11,5 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document; [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))] public abstract class DocumentControllerBase : ContentControllerBase { + protected IActionResult DocumentNotFound() => NotFound("The requested Document could not be found"); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentController.cs index 2e78dbd629..ba2cdc974e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/UpdateDocumentController.cs @@ -13,18 +13,15 @@ namespace Umbraco.Cms.Api.Management.Controllers.Document; public class UpdateDocumentController : DocumentControllerBase { - private readonly IContentService _contentService; private readonly IContentEditingService _contentEditingService; private readonly IDocumentEditingPresentationFactory _documentEditingPresentationFactory; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; public UpdateDocumentController( - IContentService contentService, IContentEditingService contentEditingService, IDocumentEditingPresentationFactory documentEditingPresentationFactory, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { - _contentService = contentService; _contentEditingService = contentEditingService; _documentEditingPresentationFactory = documentEditingPresentationFactory; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; @@ -37,10 +34,10 @@ public class UpdateDocumentController : DocumentControllerBase [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Update(Guid key, UpdateDocumentRequestModel requestModel) { - IContent? content = _contentService.GetById(key); + IContent? content = await _contentEditingService.GetAsync(key); if (content == null) { - return NotFound(); + return DocumentNotFound(); } ContentUpdateModel model = _documentEditingPresentationFactory.MapUpdateModel(requestModel); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs similarity index 100% rename from src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentControllerBase.cs rename to src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/ByKeyMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/ByKeyMediaController.cs new file mode 100644 index 0000000000..3e8700fa72 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/ByKeyMediaController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +public class ByKeyMediaController : MediaControllerBase +{ + private readonly IMediaEditingService _mediaEditingService; + private readonly IMediaPresentationModelFactory _mediaPresentationModelFactory; + + public ByKeyMediaController(IMediaEditingService mediaEditingService, IMediaPresentationModelFactory mediaPresentationModelFactory) + { + _mediaEditingService = mediaEditingService; + _mediaPresentationModelFactory = mediaPresentationModelFactory; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(DocumentResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ByKey(Guid key) + { + IMedia? media = await _mediaEditingService.GetAsync(key); + if (media == null) + { + return MediaNotFound(); + } + + MediaResponseModel model = await _mediaPresentationModelFactory.CreateResponseModelAsync(media); + return Ok(model); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs new file mode 100644 index 0000000000..92a51396dd --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +public class CreateMediaController : MediaControllerBase +{ + private readonly IMediaEditingService _mediaEditingService; + private readonly IMediaEditingPresentationFactory _mediaEditingPresentationFactory; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public CreateMediaController(IMediaEditingService mediaEditingService, IMediaEditingPresentationFactory mediaEditingPresentationFactory, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _mediaEditingService = mediaEditingService; + _mediaEditingPresentationFactory = mediaEditingPresentationFactory; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPost] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Create(CreateMediaRequestModel createRequestModel) + { + MediaCreateModel model = _mediaEditingPresentationFactory.MapCreateModel(createRequestModel); + Attempt result = await _mediaEditingService.CreateAsync(model, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + : ContentEditingOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaControllerBase.cs new file mode 100644 index 0000000000..5d9005c21b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/MediaControllerBase.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Content; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +[ApiVersion("1.0")] +[ApiController] +[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Media)] +[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))] +public class MediaControllerBase : ContentControllerBase +{ + protected IActionResult MediaNotFound() => NotFound("The requested Media could not be found"); +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/MoveToRecycleBinMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/MoveToRecycleBinMediaController.cs new file mode 100644 index 0000000000..2310ab4400 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/MoveToRecycleBinMediaController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +public class MoveToRecycleBinMediaController : MediaControllerBase +{ + private readonly IMediaEditingService _mediaEditingService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public MoveToRecycleBinMediaController(IMediaEditingService mediaEditingService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _mediaEditingService = mediaEditingService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpDelete("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task MoveToRecycleBin(Guid key) + { + Attempt result = await _mediaEditingService.MoveToRecycleBinAsync(key, CurrentUserId(_backOfficeSecurityAccessor)); + return result.Success + ? Ok() + : ContentEditingOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/UpdateMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/UpdateMediaController.cs new file mode 100644 index 0000000000..a322f04c63 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/UpdateMediaController.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Media; + +public class UpdateMediaController : MediaControllerBase +{ + private readonly IMediaEditingService _mediaEditingService; + private readonly IMediaEditingPresentationFactory _mediaEditingPresentationFactory; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public UpdateMediaController( + IMediaEditingService mediaEditingService, + IMediaEditingPresentationFactory mediaEditingPresentationFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _mediaEditingService = mediaEditingService; + _mediaEditingPresentationFactory = mediaEditingPresentationFactory; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPut("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Update(Guid key, UpdateMediaRequestModel updateRequestModel) + { + IMedia? media = await _mediaEditingService.GetAsync(key); + if (media == null) + { + return MediaNotFound(); + } + + MediaUpdateModel model = _mediaEditingPresentationFactory.MapUpdateModel(updateRequestModel); + Attempt result = await _mediaEditingService.UpdateAsync(media, model, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : ContentEditingOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs new file mode 100644 index 0000000000..5c6d65ce23 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/ByKeyMediaTypeController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType; + +public class ByKeyMediaTypeController : MediaTypeControllerBase +{ + private readonly IMediaTypeService _mediaTypeService; + private readonly IUmbracoMapper _umbracoMapper; + + public ByKeyMediaTypeController(IMediaTypeService mediaTypeService, IUmbracoMapper umbracoMapper) + { + _mediaTypeService = mediaTypeService; + _umbracoMapper = umbracoMapper; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(MediaTypeResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ByKey(Guid key) + { + // FIXME: create and use an async get method here. + IMediaType? mediaType = _mediaTypeService.Get(key); + if (mediaType == null) + { + return NotFound(); + } + + MediaTypeResponseModel model = _umbracoMapper.Map(mediaType)!; + return await Task.FromResult(Ok(model)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs new file mode 100644 index 0000000000..3609e4bbb6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/MediaTypeControllerBase.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Api.Management.Controllers.MediaType; + +[ApiVersion("1.0")] +[ApiController] +[VersionedApiBackOfficeRoute(Constants.UdiEntityType.MediaType)] +[ApiExplorerSettings(GroupName = "Media Type")] +public abstract class MediaTypeControllerBase : ManagementApiControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaBuilderExtensions.cs new file mode 100644 index 0000000000..c2ebda5895 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Mapping.Media; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class MediaBuilderExtensions +{ + internal static IUmbracoBuilder AddMedia(this IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient(); + + builder.WithCollectionBuilder().Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaTypeBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaTypeBuilderExtensions.cs new file mode 100644 index 0000000000..bebf5d8118 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/MediaTypeBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Api.Management.Mapping.MediaType; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class MediaTypeBuilderExtensions +{ + internal static IUmbracoBuilder AddMediaTypes(this IUmbracoBuilder builder) + { + builder.WithCollectionBuilder().Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IMediaEditingPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IMediaEditingPresentationFactory.cs new file mode 100644 index 0000000000..80d5600abf --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IMediaEditingPresentationFactory.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IMediaEditingPresentationFactory +{ + MediaCreateModel MapCreateModel(CreateMediaRequestModel createRequestModel); + + MediaUpdateModel MapUpdateModel(UpdateMediaRequestModel updateRequestModel); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IMediaPresentationModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IMediaPresentationModelFactory.cs new file mode 100644 index 0000000000..1c189a867b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IMediaPresentationModelFactory.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IMediaPresentationModelFactory +{ + Task CreateResponseModelAsync(IMedia media); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/MediaEditingPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/MediaEditingPresentationFactory.cs new file mode 100644 index 0000000000..5a9f305b11 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/MediaEditingPresentationFactory.cs @@ -0,0 +1,23 @@ +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace Umbraco.Cms.Api.Management.Factories; + +internal sealed class MediaEditingPresentationFactory : ContentEditingPresentationFactory, IMediaEditingPresentationFactory +{ + public MediaCreateModel MapCreateModel(CreateMediaRequestModel createRequestModel) + { + MediaCreateModel model = MapContentEditingModel(createRequestModel); + model.ContentTypeKey = createRequestModel.ContentTypeKey; + model.ParentKey = createRequestModel.ParentKey; + + return model; + } + + public MediaUpdateModel MapUpdateModel(UpdateMediaRequestModel updateRequestModel) + { + MediaUpdateModel model = MapContentEditingModel(updateRequestModel); + + return model; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationModelFactory.cs new file mode 100644 index 0000000000..cf3fd09993 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationModelFactory.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Factories; + +public class MediaPresentationModelFactory : IMediaPresentationModelFactory +{ + private readonly IUmbracoMapper _umbracoMapper; + private readonly ContentSettings _contentSettings; + private readonly MediaUrlGeneratorCollection _mediaUrlGenerators; + + public MediaPresentationModelFactory(IUmbracoMapper umbracoMapper, IOptions contentSettings, MediaUrlGeneratorCollection mediaUrlGenerators) + { + _umbracoMapper = umbracoMapper; + _contentSettings = contentSettings.Value; + _mediaUrlGenerators = mediaUrlGenerators; + } + + public Task CreateResponseModelAsync(IMedia media) + { + MediaResponseModel responseModel = _umbracoMapper.Map(media)!; + + responseModel.Urls = media + .GetUrls(_contentSettings, _mediaUrlGenerators) + .WhereNotNull() + .Select(mediaUrl => new ContentUrlInfo + { + Culture = null, + Url = mediaUrl + }) + .ToArray(); + + return Task.FromResult(responseModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs b/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs index 86e264dbc6..89db62b22c 100644 --- a/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs +++ b/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs @@ -28,6 +28,8 @@ public class ManagementApiComposer : IComposer .AddAuditLogs() .AddDocuments() .AddDocumentTypes() + .AddMedia() + .AddMediaTypes() .AddLanguages() .AddDictionary() .AddFileUpload() diff --git a/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs index c7e5797c89..9298144173 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs @@ -2,15 +2,16 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; +using ContentTypeSort = Umbraco.Cms.Api.Management.ViewModels.ContentType.ContentTypeSort; namespace Umbraco.Cms.Api.Management.Mapping.ContentType; -public abstract class ContentTypeMapDefinition +public abstract class ContentTypeMapDefinition where TContentType : IContentTypeBase - where TPropertyTypeViewModel : PropertyTypePresentationBase, new() - where TPropertyTypeContainerViewModel : PropertyTypeContainerPresentationBase, new() + where TPropertyTypeResponseModel : PropertyTypeResponseModelBase, new() + where TPropertyTypeContainerResponseModel : PropertyTypeContainerResponseModelBase, new() { - protected IEnumerable MapPropertyTypes(TContentType source) + protected IEnumerable MapPropertyTypes(TContentType source) { // create a mapping table between properties and their associated groups var groupKeysByPropertyKeys = source @@ -20,7 +21,7 @@ public abstract class ContentTypeMapDefinition map.PropertyTypeKey, map => map.GroupKey); return source.PropertyTypes.Select(propertyType => - new TPropertyTypeViewModel + new TPropertyTypeResponseModel { Key = propertyType.Key, ContainerKey = groupKeysByPropertyKeys.ContainsKey(propertyType.Key) @@ -47,7 +48,7 @@ public abstract class ContentTypeMapDefinition MapPropertyTypeContainers(TContentType source) + protected IEnumerable MapPropertyTypeContainers(TContentType source) { // create a mapping table between property group aliases and keys var groupKeysByGroupAliases = source @@ -65,7 +66,7 @@ public abstract class ContentTypeMapDefinition - new TPropertyTypeContainerViewModel + new TPropertyTypeContainerResponseModel { Key = propertyGroup.Key, ParentKey = ParentGroupKey(propertyGroup), @@ -75,4 +76,17 @@ public abstract class ContentTypeMapDefinition MapAllowedContentTypes(TContentType source) + => source.AllowedContentTypes?.Select(contentTypeSort => new ContentTypeSort { Key = contentTypeSort.Key, SortOrder = contentTypeSort.SortOrder }).ToArray() + ?? Array.Empty(); + + protected IEnumerable MapCompositions(TContentType source, IEnumerable contentTypeComposition) + => contentTypeComposition.Select(contentType => new ContentTypeComposition + { + Key = contentType.Key, + CompositionType = contentType.Id == source.ParentId + ? ContentTypeCompositionType.Inheritance + : ContentTypeCompositionType.Composition + }).ToArray(); } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs index 078d8f4d93..e6092bfa6b 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs @@ -4,11 +4,10 @@ using Umbraco.Cms.Api.Management.ViewModels.DocumentType; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Extensions; -using ContentTypeSort = Umbraco.Cms.Api.Management.ViewModels.ContentType.ContentTypeSort; namespace Umbraco.Cms.Api.Management.Mapping.DocumentType; -public class DocumentTypeMapDefinition : ContentTypeMapDefinition, IMapDefinition +public class DocumentTypeMapDefinition : ContentTypeMapDefinition, IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) => mapper.Define((_, _) => new DocumentTypeResponseModel(), Map); @@ -27,12 +26,8 @@ public class DocumentTypeMapDefinition : ContentTypeMapDefinition new ContentTypeSort { Key = contentTypeSort.Key, SortOrder = contentTypeSort.SortOrder }); - } + target.AllowedContentTypes = MapAllowedContentTypes(source); + target.Compositions = MapCompositions(source, source.ContentTypeComposition); if (source.AllowedTemplates != null) { @@ -50,13 +45,5 @@ public class DocumentTypeMapDefinition : ContentTypeMapDefinition new ContentTypeComposition - { - Key = contentType.Key, - CompositionType = contentType.Id == source.ParentId - ? ContentTypeCompositionType.Inheritance - : ContentTypeCompositionType.Composition - }).ToArray(); } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Media/MediaMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Media/MediaMapDefinition.cs new file mode 100644 index 0000000000..ba53f950b0 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Media/MediaMapDefinition.cs @@ -0,0 +1,27 @@ +using Umbraco.Cms.Api.Management.Mapping.Content; +using Umbraco.Cms.Api.Management.ViewModels.Media; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Api.Management.Mapping.Media; + +public class MediaMapDefinition : ContentMapDefinition, IMapDefinition +{ + public MediaMapDefinition(PropertyEditorCollection propertyEditorCollection) + : base(propertyEditorCollection) + { + } + + public void DefineMaps(IUmbracoMapper mapper) + => mapper.Define((_, _) => new MediaResponseModel(), Map); + + // Umbraco.Code.MapAll -Urls + private void Map(IMedia source, MediaResponseModel target, MapperContext context) + { + target.Key = source.Key; + target.ContentTypeKey = source.ContentType.Key; + target.Values = MapValueViewModels(source); + target.Variants = MapVariantViewModels(source); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs new file mode 100644 index 0000000000..de465cc98c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs @@ -0,0 +1,31 @@ +using Umbraco.Cms.Api.Management.Mapping.ContentType; +using Umbraco.Cms.Api.Management.ViewModels.MediaType; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Mapping.MediaType; + +public class MediaTypeMapDefinition : ContentTypeMapDefinition, IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + => mapper.Define((_, _) => new MediaTypeResponseModel(), Map); + + // Umbraco.Code.MapAll + private void Map(IMediaType source, MediaTypeResponseModel target, MapperContext context) + { + target.Key = source.Key; + target.Alias = source.Alias; + target.Name = source.Name ?? string.Empty; + target.Description = source.Description; + target.Icon = source.Icon ?? string.Empty; + target.AllowedAsRoot = source.AllowedAsRoot; + target.VariesByCulture = source.VariesByCulture(); + target.VariesBySegment = source.VariesBySegment(); + target.IsElement = source.IsElement; + target.Containers = MapPropertyTypeContainers(source); + target.Properties = MapPropertyTypes(source); + target.AllowedContentTypes = MapAllowedContentTypes(source); + target.Compositions = MapCompositions(source, source.ContentTypeComposition); + } +} diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 15976e8579..4f97eaed84 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -3127,6 +3127,44 @@ } } }, + "/umbraco/management/api/v1/media-type/{key}": { + "get": { + "tags": [ + "Media Type" + ], + "operationId": "GetMediaTypeByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaTypeResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found" + } + } + } + }, "/umbraco/management/api/v1/tree/media-type/children": { "get": { "tags": [ @@ -3274,6 +3312,155 @@ } } }, + "/umbraco/management/api/v1/media": { + "post": { + "tags": [ + "Media" + ], + "operationId": "PostMedia", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateMediaRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/umbraco/management/api/v1/media/{key}": { + "get": { + "tags": [ + "Media" + ], + "operationId": "GetMediaByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "Media" + ], + "operationId": "DeleteMediaByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "Media" + ], + "operationId": "PutMediaByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateMediaRequestModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + } + } + }, "/umbraco/management/api/v1/recycle-bin/media/children": { "get": { "tags": [ @@ -6560,37 +6747,6 @@ }, "additionalProperties": false }, - "ContentCreateRequestModelBaseDocumentValueModelDocumentVariantRequestModel": { - "type": "object", - "properties": { - "values": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/DocumentValueModel" - } - ] - } - }, - "variants": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/DocumentVariantRequestModel" - } - ] - } - }, - "parentKey": { - "type": "string", - "format": "uuid", - "nullable": true - } - }, - "additionalProperties": false - }, "ContentResponseModelBaseDocumentValueModelDocumentVariantResponseModel": { "type": "object", "properties": { @@ -6705,7 +6861,7 @@ "type": "integer", "format": "int32" }, - "ContentTypePresentationBaseDocumentTypePropertyTypePresentationBaseDocumentTypePropertyTypeContainerPresentationBaseModel": { + "ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel": { "type": "object", "properties": { "key": { @@ -6742,7 +6898,7 @@ "items": { "oneOf": [ { - "$ref": "#/components/schemas/DocumentTypePropertyTypePresentationBaseModel" + "$ref": "#/components/schemas/DocumentTypePropertyTypeResponseModel" } ] } @@ -6752,7 +6908,7 @@ "items": { "oneOf": [ { - "$ref": "#/components/schemas/DocumentTypePropertyTypeContainerPresentationBaseModel" + "$ref": "#/components/schemas/DocumentTypePropertyTypeContainerResponseModel" } ] } @@ -6776,13 +6932,81 @@ } ] } + } + }, + "additionalProperties": false + }, + "ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" }, - "cleanup": { - "oneOf": [ - { - "$ref": "#/components/schemas/ContentTypeCleanupModel" - } - ] + "alias": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "icon": { + "type": "string" + }, + "allowedAsRoot": { + "type": "boolean" + }, + "variesByCulture": { + "type": "boolean" + }, + "variesBySegment": { + "type": "boolean" + }, + "isElement": { + "type": "boolean" + }, + "properties": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaTypePropertyTypeResponseModel" + } + ] + } + }, + "containers": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaTypePropertyTypeContainerResponseModel" + } + ] + } + }, + "allowedContentTypes": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeSortModel" + } + ] + } + }, + "compositions": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeCompositionModel" + } + ] + } } }, "additionalProperties": false @@ -6801,32 +7025,6 @@ }, "additionalProperties": false }, - "ContentUpdateRequestModelBaseDocumentValueModelDocumentVariantRequestModel": { - "type": "object", - "properties": { - "values": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/DocumentValueModel" - } - ] - } - }, - "variants": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/DocumentVariantRequestModel" - } - ] - } - } - }, - "additionalProperties": false - }, "ContentUrlInfoModel": { "type": "object", "properties": { @@ -6851,6 +7049,68 @@ }, "additionalProperties": false }, + "CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentValueModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentVariantRequestModel" + } + ] + } + }, + "parentKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaValueModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaVariantRequestModel" + } + ] + } + }, + "parentKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, "CreateDataTypeRequestModel": { "type": "object", "allOf": [ @@ -6887,7 +7147,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/ContentCreateRequestModelBaseDocumentValueModelDocumentVariantRequestModel" + "$ref": "#/components/schemas/CreateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel" } ], "properties": { @@ -6933,6 +7193,21 @@ }, "additionalProperties": false }, + "CreateMediaRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/CreateContentRequestModelBaseMediaValueModelMediaVariantRequestModel" + } + ], + "properties": { + "contentTypeKey": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, "CreatePackageRequestModel": { "type": "object", "allOf": [ @@ -7349,20 +7624,20 @@ } } }, - "DocumentTypePropertyTypeContainerPresentationBaseModel": { + "DocumentTypePropertyTypeContainerResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/PropertyTypeContainerPresentationBaseModel" + "$ref": "#/components/schemas/PropertyTypeContainerResponseModelBaseModel" } ], "additionalProperties": false }, - "DocumentTypePropertyTypePresentationBaseModel": { + "DocumentTypePropertyTypeResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/PropertyTypePresentationBaseModel" + "$ref": "#/components/schemas/PropertyTypeResponseModelBaseModel" } ], "additionalProperties": false @@ -7371,7 +7646,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/ContentTypePresentationBaseDocumentTypePropertyTypePresentationBaseDocumentTypePropertyTypeContainerPresentationBaseModel" + "$ref": "#/components/schemas/ContentTypeResponseModelBaseDocumentTypePropertyTypeResponseModelDocumentTypePropertyTypeContainerResponseModel" } ], "properties": { @@ -7386,6 +7661,13 @@ "type": "string", "format": "uuid", "nullable": true + }, + "cleanup": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeCleanupModel" + } + ] } }, "additionalProperties": false @@ -8099,6 +8381,102 @@ }, "additionalProperties": false }, + "MediaTypePropertyTypeContainerResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PropertyTypeContainerResponseModelBaseModel" + } + ], + "additionalProperties": false + }, + "MediaTypePropertyTypeResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PropertyTypeResponseModelBaseModel" + } + ], + "additionalProperties": false + }, + "MediaTypeResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ContentTypeResponseModelBaseMediaTypePropertyTypeResponseModelMediaTypePropertyTypeContainerResponseModel" + } + ], + "additionalProperties": false + }, + "MediaValueModel": { + "required": [ + "$type" + ], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ValueModelBaseModel" + } + ], + "properties": { + "$type": { + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "MediaValueModel": "#/components/schemas/MediaValueModel" + } + } + }, + "MediaVariantRequestModel": { + "required": [ + "$type" + ], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/VariantModelBaseModel" + } + ], + "properties": { + "$type": { + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "MediaVariantRequestModel": "#/components/schemas/MediaVariantRequestModel" + } + } + }, + "MediaVariantResponseModel": { + "required": [ + "$type" + ], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/VariantResponseModelBaseModel" + } + ], + "properties": { + "$type": { + "type": "string" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "MediaVariantResponseModel": "#/components/schemas/MediaVariantResponseModel" + } + } + }, "ModelsBuilderResponseModel": { "type": "object", "properties": { @@ -9139,7 +9517,7 @@ }, "additionalProperties": false }, - "PropertyTypeContainerPresentationBaseModel": { + "PropertyTypeContainerResponseModelBaseModel": { "type": "object", "properties": { "key": { @@ -9165,7 +9543,7 @@ }, "additionalProperties": false }, - "PropertyTypePresentationBaseModel": { + "PropertyTypeResponseModelBaseModel": { "type": "object", "properties": { "key": { @@ -9843,6 +10221,58 @@ }, "additionalProperties": false }, + "UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentValueModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentVariantRequestModel" + } + ] + } + } + }, + "additionalProperties": false + }, + "UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaValueModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/MediaVariantRequestModel" + } + ] + } + } + }, + "additionalProperties": false + }, "UpdateDataTypeRequestModel": { "type": "object", "allOf": [ @@ -9865,7 +10295,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/ContentUpdateRequestModelBaseDocumentValueModelDocumentVariantRequestModel" + "$ref": "#/components/schemas/UpdateContentRequestModelBaseDocumentValueModelDocumentVariantRequestModel" } ], "properties": { @@ -9895,6 +10325,15 @@ ], "additionalProperties": false }, + "UpdateMediaRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/UpdateContentRequestModelBaseMediaValueModelMediaVariantRequestModel" + } + ], + "additionalProperties": false + }, "UpdatePackageRequestModel": { "type": "object", "allOf": [ @@ -10131,6 +10570,7 @@ "discriminator": { "propertyName": "$type", "mapping": { + "MediaValueModel": "#/components/schemas/MediaValueModel", "DocumentValueModel": "#/components/schemas/DocumentValueModel" } } @@ -10160,6 +10600,8 @@ "discriminator": { "propertyName": "$type", "mapping": { + "MediaVariantRequestModel": "#/components/schemas/MediaVariantRequestModel", + "MediaVariantResponseModel": "#/components/schemas/MediaVariantResponseModel", "DocumentVariantRequestModel": "#/components/schemas/DocumentVariantRequestModel", "DocumentVariantResponseModel": "#/components/schemas/DocumentVariantResponseModel" } @@ -10198,6 +10640,7 @@ "discriminator": { "propertyName": "$type", "mapping": { + "MediaVariantResponseModel": "#/components/schemas/MediaVariantResponseModel", "DocumentVariantResponseModel": "#/components/schemas/DocumentVariantResponseModel" } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentCreateRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/CreateContentRequestModelBase.cs similarity index 79% rename from src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentCreateRequestModelBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/Content/CreateContentRequestModelBase.cs index be12938dab..b0192a9221 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentCreateRequestModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/CreateContentRequestModelBase.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Content; -public abstract class ContentCreateRequestModelBase +public abstract class CreateContentRequestModelBase : ContentModelBase where TValueModel : ValueModelBase where TVariantModel : VariantModelBase diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUpdateRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/UpdateContentRequestModelBase.cs similarity index 76% rename from src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUpdateRequestModelBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/Content/UpdateContentRequestModelBase.cs index d0834ff57a..2647c6e023 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUpdateRequestModelBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/UpdateContentRequestModelBase.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Content; -public abstract class ContentUpdateRequestModelBase +public abstract class UpdateContentRequestModelBase : ContentModelBase where TValueModel : ValueModelBase where TVariantModel : VariantModelBase diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypePresentationBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeResponseModelBase.cs similarity index 76% rename from src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypePresentationBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeResponseModelBase.cs index 9249eebc87..dcf8421dfe 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypePresentationBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeResponseModelBase.cs @@ -1,8 +1,8 @@ namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; -public abstract class ContentTypePresentationBase - where TPropertyType : PropertyTypePresentationBase - where TPropertyTypeContainer : PropertyTypeContainerPresentationBase +public abstract class ContentTypeResponseModelBase + where TPropertyType : PropertyTypeResponseModelBase + where TPropertyTypeContainer : PropertyTypeContainerResponseModelBase { public Guid Key { get; set; } @@ -29,6 +29,4 @@ public abstract class ContentTypePresentationBase AllowedContentTypes { get; set; } = Array.Empty(); public IEnumerable Compositions { get; set; } = Array.Empty(); - - public ContentTypeCleanup Cleanup { get; set; } = new(); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerPresentationBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerResponseModelBase.cs similarity index 86% rename from src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerPresentationBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerResponseModelBase.cs index 380b72752f..d71dd6c4da 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerPresentationBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerResponseModelBase.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; -public abstract class PropertyTypeContainerPresentationBase +public abstract class PropertyTypeContainerResponseModelBase { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypePresentationBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeResponseModelBase.cs similarity index 91% rename from src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypePresentationBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeResponseModelBase.cs index b5456163b8..2384495230 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypePresentationBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeResponseModelBase.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; -public abstract class PropertyTypePresentationBase +public abstract class PropertyTypeResponseModelBase { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/CreateDocumentRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/CreateDocumentRequestModel.cs index 121a4a3f3b..e9b4a52800 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/CreateDocumentRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/CreateDocumentRequestModel.cs @@ -2,7 +2,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document; -public class CreateDocumentRequestModel : ContentCreateRequestModelBase +public class CreateDocumentRequestModel : CreateContentRequestModelBase { public Guid ContentTypeKey { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/UpdateDocumentRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/UpdateDocumentRequestModel.cs index 1ef008d1a4..9ee6fdca0d 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/UpdateDocumentRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/UpdateDocumentRequestModel.cs @@ -2,7 +2,7 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Document; -public class UpdateDocumentRequestModel : ContentUpdateRequestModelBase +public class UpdateDocumentRequestModel : UpdateContentRequestModelBase { public Guid? TemplateKey { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerPresentationBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerResponseModel.cs similarity index 55% rename from src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerPresentationBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerResponseModel.cs index ad3f68f1b4..a8c63b43bf 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerPresentationBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerResponseModel.cs @@ -2,6 +2,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; -public class DocumentTypePropertyTypeContainerPresentationBase : PropertyTypeContainerPresentationBase +public class DocumentTypePropertyTypeContainerResponseModel : PropertyTypeContainerResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypePresentationBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeResponseModel.cs similarity index 60% rename from src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypePresentationBase.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeResponseModel.cs index 45f1de983f..496ef6e2e3 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypePresentationBase.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeResponseModel.cs @@ -2,6 +2,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; -public class DocumentTypePropertyTypePresentationBase : PropertyTypePresentationBase +public class DocumentTypePropertyTypeResponseModel : PropertyTypeResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeResponseModel.cs index f0a5780f52..28e5c1d795 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeResponseModel.cs @@ -2,9 +2,11 @@ namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; -public class DocumentTypeResponseModel : ContentTypePresentationBase +public class DocumentTypeResponseModel : ContentTypeResponseModelBase { public IEnumerable AllowedTemplateKeys { get; set; } = Array.Empty(); public Guid? DefaultTemplateKey { get; set; } + + public ContentTypeCleanup Cleanup { get; set; } = new(); } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/CreateMediaRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/CreateMediaRequestModel.cs new file mode 100644 index 0000000000..a4a4ec3ead --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/CreateMediaRequestModel.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class CreateMediaRequestModel : CreateContentRequestModelBase +{ + public Guid ContentTypeKey { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaResponseModel.cs new file mode 100644 index 0000000000..384e4e2895 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaResponseModel.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class MediaResponseModel : ContentResponseModelBase +{ + public IEnumerable Urls { get; set; } = Array.Empty(); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaValueModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaValueModel.cs new file mode 100644 index 0000000000..6b235f8e7b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaValueModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class MediaValueModel : ValueModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantRequestModel.cs new file mode 100644 index 0000000000..906a6f260d --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class MediaVariantRequestModel : VariantModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantResponseModel.cs new file mode 100644 index 0000000000..58039dd61f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/MediaVariantResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class MediaVariantResponseModel : VariantResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/UpdateMediaRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/UpdateMediaRequestModel.cs new file mode 100644 index 0000000000..54af0e7c0e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/UpdateMediaRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Media; + +public class UpdateMediaRequestModel : UpdateContentRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeContainerResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeContainerResponseModel.cs new file mode 100644 index 0000000000..a108cbab34 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeContainerResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.MediaType; + +public class MediaTypePropertyTypeContainerResponseModel : PropertyTypeContainerResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeResponseModel.cs new file mode 100644 index 0000000000..e68abd71a8 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypePropertyTypeResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.MediaType; + +public class MediaTypePropertyTypeResponseModel : PropertyTypeResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs new file mode 100644 index 0000000000..4d0aca9516 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.MediaType; + +public class MediaTypeResponseModel : ContentTypeResponseModelBase +{ +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index ffbbfebd4a..ab7b8b6026 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -299,6 +299,7 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); + Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaCreateModel.cs b/src/Umbraco.Core/Models/ContentEditing/MediaCreateModel.cs new file mode 100644 index 0000000000..d6495f1afa --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/MediaCreateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public class MediaCreateModel : ContentCreationModelBase +{ +} diff --git a/src/Umbraco.Core/Models/ContentEditing/MediaUpdateModel.cs b/src/Umbraco.Core/Models/ContentEditing/MediaUpdateModel.cs new file mode 100644 index 0000000000..29d85e33b5 --- /dev/null +++ b/src/Umbraco.Core/Models/ContentEditing/MediaUpdateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.ContentEditing; + +public class MediaUpdateModel : ContentEditingModelBase +{ +} diff --git a/src/Umbraco.Core/Services/ContentEditingService.cs b/src/Umbraco.Core/Services/ContentEditingService.cs index 9c289ff665..4b70e88bb0 100644 --- a/src/Umbraco.Core/Services/ContentEditingService.cs +++ b/src/Umbraco.Core/Services/ContentEditingService.cs @@ -12,7 +12,6 @@ internal sealed class ContentEditingService { private readonly ITemplateService _templateService; private readonly ILogger _logger; - private readonly ICoreScopeProvider _scopeProvider; public ContentEditingService( IContentService contentService, @@ -22,11 +21,10 @@ internal sealed class ContentEditingService ITemplateService templateService, ILogger logger, ICoreScopeProvider scopeProvider) - : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger) + : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider) { _templateService = templateService; _logger = logger; - _scopeProvider = scopeProvider; } public async Task GetAsync(Guid id) @@ -77,10 +75,10 @@ internal sealed class ContentEditingService } public async Task> MoveToRecycleBinAsync(Guid id, int userId = Constants.Security.SuperUserId) - => await HandleDeletionAsync(id, content => ContentService.MoveToRecycleBin(content, userId)); + => await HandleDeletionAsync(id, content => ContentService.MoveToRecycleBin(content, userId), false); public async Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId) - => await HandleDeletionAsync(id, content => ContentService.Delete(content, userId)); + => await HandleDeletionAsync(id, content => ContentService.Delete(content, userId), true); protected override IContent Create(string? name, int parentId, IContentType contentType) => new Content(name, parentId, contentType); @@ -130,26 +128,4 @@ internal sealed class ContentEditingService return ContentEditingOperationStatus.Unknown; } } - - // helper method to perform move-to-recycle-bin and delete for content as they are very much handled in the same way - private async Task> HandleDeletionAsync(Guid id, Func performDelete) - { - using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete:true); - IContent? content = ContentService.GetById(id); - if (content == null) - { - return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content)); - } - - OperationResult deleteResult = performDelete(content); - return deleteResult.Result switch - { - // these are the only result states currently expected from Delete - OperationResultType.Success => Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, content), - OperationResultType.FailedCancelledByEvent => Attempt.FailWithStatus(ContentEditingOperationStatus.CancelledByNotification, content), - - // for any other state we'll return "unknown" so we know that we need to amend this - _ => Attempt.FailWithStatus(ContentEditingOperationStatus.Unknown, content) - }; - } } diff --git a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs index 93bf43c264..ed680dd3e1 100644 --- a/src/Umbraco.Core/Services/ContentEditingServiceBase.cs +++ b/src/Umbraco.Core/Services/ContentEditingServiceBase.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Extensions; @@ -20,16 +21,20 @@ public abstract class ContentEditingServiceBase> _logger; + private readonly ICoreScopeProvider _scopeProvider; + protected ContentEditingServiceBase( TContentService contentService, TContentTypeService contentTypeService, PropertyEditorCollection propertyEditorCollection, IDataTypeService dataTypeService, - ILogger> logger) + ILogger> logger, + ICoreScopeProvider scopeProvider) { _propertyEditorCollection = propertyEditorCollection; _dataTypeService = dataTypeService; _logger = logger; + _scopeProvider = scopeProvider; ContentService = contentService; ContentTypeService = contentTypeService; } @@ -77,6 +82,33 @@ public abstract class ContentEditingServiceBase> HandleDeletionAsync(Guid id, Func performDelete, bool allowForTrashed) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete:true); + TContent? content = ContentService.GetById(id); + if (content == null) + { + return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, content)); + } + + if (content.Trashed && allowForTrashed is false) + { + return await Task.FromResult(Attempt.FailWithStatus(ContentEditingOperationStatus.InTrash, content)); + } + + OperationResult? deleteResult = performDelete(content); + return deleteResult?.Result switch + { + // these are the only result states currently expected from Delete + OperationResultType.Success => Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, content), + OperationResultType.FailedCancelledByEvent => Attempt.FailWithStatus(ContentEditingOperationStatus.CancelledByNotification, content), + + // for any other state we'll return "unknown" so we know that we need to amend this + _ => Attempt.FailWithStatus(ContentEditingOperationStatus.Unknown, content) + }; + } + private TContentType? TryGetAndValidateContentType(Guid contentTypeKey, ContentEditingModelBase contentEditingModelBase, out ContentEditingOperationStatus operationStatus) { TContentType? contentType = ContentTypeService.Get(contentTypeKey); diff --git a/src/Umbraco.Core/Services/IMediaEditingService.cs b/src/Umbraco.Core/Services/IMediaEditingService.cs new file mode 100644 index 0000000000..0187ca6977 --- /dev/null +++ b/src/Umbraco.Core/Services/IMediaEditingService.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +public interface IMediaEditingService +{ + Task GetAsync(Guid id); + + Task> CreateAsync(MediaCreateModel createModel, int userId = Constants.Security.SuperUserId); + + Task> UpdateAsync(IMedia content, MediaUpdateModel updateModel, int userId = Constants.Security.SuperUserId); + + Task> MoveToRecycleBinAsync(Guid id, int userId = Constants.Security.SuperUserId); + + Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId); +} diff --git a/src/Umbraco.Core/Services/MediaEditingService.cs b/src/Umbraco.Core/Services/MediaEditingService.cs new file mode 100644 index 0000000000..3f8fa20e47 --- /dev/null +++ b/src/Umbraco.Core/Services/MediaEditingService.cs @@ -0,0 +1,91 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services; + +internal sealed class MediaEditingService + : ContentEditingServiceBase, IMediaEditingService +{ + private readonly ILogger> _logger; + + public MediaEditingService( + IMediaService contentService, + IMediaTypeService contentTypeService, + PropertyEditorCollection propertyEditorCollection, + IDataTypeService dataTypeService, + ILogger> logger, + ICoreScopeProvider scopeProvider) + : base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider) => + _logger = logger; + + public async Task GetAsync(Guid id) + { + IMedia? media = ContentService.GetById(id); + return await Task.FromResult(media); + } + + public async Task> CreateAsync(MediaCreateModel createModel, int userId = Constants.Security.SuperUserId) + { + Attempt result = await MapCreate(createModel); + if (result.Success == false) + { + return result; + } + + IMedia media = result.Result!; + + ContentEditingOperationStatus operationStatus = Save(media, userId); + return operationStatus == ContentEditingOperationStatus.Success + ? Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, media) + : Attempt.FailWithStatus(operationStatus, media); + } + + public async Task> UpdateAsync(IMedia content, MediaUpdateModel updateModel, int userId = Constants.Security.SuperUserId) + { + Attempt result = await MapUpdate(content, updateModel); + if (result.Success == false) + { + return Attempt.FailWithStatus(result.Result, content); + } + + ContentEditingOperationStatus operationStatus = Save(content, userId); + return operationStatus == ContentEditingOperationStatus.Success + ? Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, content) + : Attempt.FailWithStatus(operationStatus, content); + } + + public async Task> MoveToRecycleBinAsync(Guid id, int userId = Constants.Security.SuperUserId) + => await HandleDeletionAsync(id, media => ContentService.MoveToRecycleBin(media, userId).Result, false); + + public async Task> DeleteAsync(Guid id, int userId = Constants.Security.SuperUserId) + => await HandleDeletionAsync(id, media => ContentService.Delete(media, userId).Result, true); + + protected override IMedia Create(string? name, int parentId, IMediaType contentType) + => new Models.Media(name, parentId, contentType); + + private ContentEditingOperationStatus Save(IMedia media, int userId) + { + try + { + Attempt saveResult = ContentService.Save(media, userId); + return saveResult.Result?.Result switch + { + // these are the only result states currently expected from Save + OperationResultType.Success => ContentEditingOperationStatus.Success, + OperationResultType.FailedCancelledByEvent => ContentEditingOperationStatus.CancelledByNotification, + + // for any other state we'll return "unknown" so we know that we need to amend this + _ => ContentEditingOperationStatus.Unknown + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Media save operation failed"); + return ContentEditingOperationStatus.Unknown; + } + } +} diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs index 7040f30bae..5555a9938f 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentEditingOperationStatus.cs @@ -12,5 +12,6 @@ public enum ContentEditingOperationStatus TemplateNotFound, TemplateNotAllowed, PropertyTypeNotFound, + InTrash, Unknown }