Files
Umbraco-CMS/src/Umbraco.Core/Services/ContentBlueprintEditingService.cs
Elitsa Marinovska 3edc783ddb V14: Additional blueprint endpoints (#16047)
* Fix /item endpoint response when 0 ids

* Adding GetPagedByContentTypeKeyAsync to IContentBlueprintEditingService

* Adding mapping for new DocumentTypeBlueprintItemResponseModel

* New endpoint and operation status handling

* Fix test cases

* Added tests

* Fixed partial test references

* Rename method

* Add ancestors endpoint for blueprints

* Change == to is

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
2024-04-15 13:49:08 +02:00

232 lines
9.7 KiB
C#

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 ContentBlueprintEditingService
: ContentEditingServiceBase<IContent, IContentType, IContentService, IContentTypeService>, IContentBlueprintEditingService
{
private readonly IContentBlueprintContainerService _containerService;
public ContentBlueprintEditingService(
IContentService contentService,
IContentTypeService contentTypeService,
PropertyEditorCollection propertyEditorCollection,
IDataTypeService dataTypeService,
ILogger<ContentEditingServiceBase<IContent, IContentType, IContentService, IContentTypeService>> logger,
ICoreScopeProvider scopeProvider,
IUserIdKeyResolver userIdKeyResolver,
IContentValidationService validationService,
IContentBlueprintContainerService containerService)
: base(contentService, contentTypeService, propertyEditorCollection, dataTypeService, logger, scopeProvider, userIdKeyResolver, validationService)
=> _containerService = containerService;
public async Task<IContent?> GetAsync(Guid key)
{
IContent? blueprint = ContentService.GetBlueprintById(key);
return await Task.FromResult(blueprint);
}
public async Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take)
{
IContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey);
if (contentType is null)
{
return Attempt.FailWithStatus<PagedModel<IContent>?, ContentEditingOperationStatus>(ContentEditingOperationStatus.ContentTypeNotFound, null);
}
IContent[] blueprints = ContentService.GetBlueprintsForContentTypes([contentType.Id]).ToArray();
var result = new PagedModel<IContent>
{
Items = blueprints.Skip(skip).Take(take),
Total = blueprints.Length,
};
return Attempt.SucceedWithStatus<PagedModel<IContent>?, ContentEditingOperationStatus>(ContentEditingOperationStatus.Success, result);
}
public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateAsync(ContentBlueprintCreateModel createModel, Guid userKey)
{
if (await ValidateCulturesAsync(createModel) is false)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.InvalidCulture, new ContentCreateResult());
}
Attempt<ContentCreateResult, ContentEditingOperationStatus> result = await MapCreate<ContentCreateResult>(createModel);
if (result.Success is false)
{
return result;
}
IContent blueprint = result.Result.Content!;
if (ValidateUniqueName(createModel.InvariantName ?? string.Empty, blueprint) is false)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.DuplicateName, new ContentCreateResult());
}
// Save blueprint
await SaveAsync(blueprint, userKey);
return Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, new ContentCreateResult { Content = blueprint, ValidationResult = result.Result.ValidationResult });
}
public async Task<Attempt<ContentCreateResult, ContentEditingOperationStatus>> CreateFromContentAsync(Guid contentKey, string name, Guid? key, Guid userKey)
{
IContent? content = ContentService.GetById(contentKey);
if (content is null)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentCreateResult());
}
if (ValidateUniqueName(name, content) is false)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.DuplicateName, new ContentCreateResult());
}
// Create Blueprint
var currentUserId = await GetUserIdAsync(userKey);
IContent blueprint = ContentService.CreateContentFromBlueprint(content, name, currentUserId);
if (key.HasValue)
{
blueprint.Key = key.Value;
}
// Save blueprint
await SaveAsync(blueprint, userKey);
return Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, new ContentCreateResult { Content = blueprint });
}
public async Task<Attempt<ContentUpdateResult, ContentEditingOperationStatus>> UpdateAsync(Guid key, ContentBlueprintUpdateModel updateModel, Guid userKey)
{
IContent? blueprint = await GetAsync(key);
if (blueprint is null)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, new ContentUpdateResult());
}
if (ValidateUniqueName(updateModel.InvariantName ?? string.Empty, blueprint) is false)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.DuplicateName, new ContentUpdateResult());
}
if (await ValidateCulturesAsync(updateModel) is false)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.InvalidCulture, new ContentUpdateResult { Content = blueprint });
}
Attempt<ContentUpdateResult, ContentEditingOperationStatus> result = await MapUpdate<ContentUpdateResult>(blueprint, updateModel);
if (result.Success is false)
{
return Attempt.FailWithStatus(result.Status, result.Result);
}
// Save blueprint
await SaveAsync(blueprint, userKey);
return Attempt.SucceedWithStatus(ContentEditingOperationStatus.Success, new ContentUpdateResult { Content = blueprint, ValidationResult = result.Result.ValidationResult });
}
public async Task<Attempt<IContent?, ContentEditingOperationStatus>> DeleteAsync(Guid key, Guid userKey)
{
using ICoreScope scope = CoreScopeProvider.CreateCoreScope();
IContent? blueprint = await GetAsync(key);
if (blueprint is null)
{
return Attempt.FailWithStatus(ContentEditingOperationStatus.NotFound, blueprint);
}
// Delete blueprint
var performingUserId = await GetUserIdAsync(userKey);
ContentService.DeleteBlueprint(blueprint, performingUserId);
scope.Complete();
return Attempt.SucceedWithStatus<IContent?, ContentEditingOperationStatus>(ContentEditingOperationStatus.Success, blueprint);
}
public async Task<Attempt<ContentEditingOperationStatus>> MoveAsync(Guid key, Guid? containerKey, Guid userKey)
{
using ICoreScope scope = CoreScopeProvider.CreateCoreScope();
IContent? toMove = await GetAsync(key);
if (toMove is null)
{
return Attempt.Fail(ContentEditingOperationStatus.NotFound);
}
var parentId = Constants.System.Root;
if (containerKey.HasValue && containerKey.Value != Guid.Empty)
{
EntityContainer? container = await _containerService.GetAsync(containerKey.Value);
if (container is null)
{
return Attempt.Fail(ContentEditingOperationStatus.ParentNotFound);
}
parentId = container.Id;
}
if (toMove.ParentId == parentId)
{
return Attempt.Succeed(ContentEditingOperationStatus.Success);
}
// NOTE: as long as the parent ID is correct the document repo takes care of updating the rest of the
// structural node data like path, level, sort orders etc.
toMove.ParentId = parentId;
// Save blueprint
await SaveAsync(toMove, userKey);
scope.Complete();
return Attempt.Succeed(ContentEditingOperationStatus.Success);
}
protected override IContent New(string? name, int parentId, IContentType contentType)
=> new Content(name, parentId, contentType);
protected override async Task<(int? ParentId, ContentEditingOperationStatus OperationStatus)> TryGetAndValidateParentIdAsync(Guid? parentKey, IContentType contentType)
{
if (parentKey.HasValue is false)
{
return (Constants.System.Root, ContentEditingOperationStatus.Success);
}
EntityContainer? container = await _containerService.GetAsync(parentKey.Value);
return container is not null
? (container.Id, ContentEditingOperationStatus.Success)
: (null, ContentEditingOperationStatus.ParentNotFound);
}
/// <summary>
/// NB: Some methods from ContentEditingServiceBase are needed, so we need to inherit from it
/// but there are others that are not required to be implemented in the case of blueprints, therefore they throw NotImplementedException as default.
/// </summary>
protected override OperationResult? Move(IContent content, int newParentId, int userId) => throw new NotImplementedException();
protected override IContent? Copy(IContent content, int newParentId, bool relateToOriginal, bool includeDescendants, int userId) => throw new NotImplementedException();
protected override OperationResult? MoveToRecycleBin(IContent content, int userId) => throw new NotImplementedException();
protected override OperationResult? Delete(IContent content, int userId) => throw new NotImplementedException();
private async Task SaveAsync(IContent blueprint, Guid userKey)
{
var currentUserId = await GetUserIdAsync(userKey);
ContentService.SaveBlueprint(blueprint, currentUserId);
}
private bool ValidateUniqueName(string name, IContent content)
{
IEnumerable<IContent> existing = ContentService.GetBlueprintsForContentTypes(content.ContentTypeId);
return existing.Any(c => c.Name == name && c.Id != content.Id) is false;
}
}