From d2707acd3b6b7e10dca50a06da1fc4b9505bb281 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:29:42 +0200 Subject: [PATCH] V14: Create seperate Create and Update in ContentTypeServiceBase (#16714) * Create seperate Create and Update in ContentTypeServiceBase * Fix up notification for update --------- Co-authored-by: nikolajlauridsen --- .../DocumentTypeControllerBase.cs | 12 ++++ .../ContentTypeEditingService.cs | 16 +++-- ...peServiceBaseOfTRepositoryTItemTService.cs | 69 +++++++++++++++++++ .../Services/IContentTypeServiceBase.cs | 15 ++++ .../ContentTypeOperationStatus.cs | 5 +- 5 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs index 2e5b7385ae..c65ed1631d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentTypeControllerBase.cs @@ -86,6 +86,18 @@ public abstract class DocumentTypeControllerBase : ManagementApiControllerBase .WithTitle("Operation not permitted") .WithDetail("The attempted operation was not permitted, likely due to a permission/configuration mismatch with the operation.") .Build()), + ContentTypeOperationStatus.CancelledByNotification => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Cancelled by notification") + .WithDetail("The attempted operation was cancelled by a notification.") + .Build()), + ContentTypeOperationStatus.NameCannotBeEmpty => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Name cannot be empty") + .WithDetail("The name of the content type cannot be empty") + .Build()), + ContentTypeOperationStatus.NameTooLong => new BadRequestObjectResult(problemDetailsBuilder + .WithTitle("Name was too long") + .WithDetail("Name cannot be more than 255 characters in length.") + .Build()), _ => new ObjectResult("Unknown content type operation status") { StatusCode = StatusCodes.Status500InternalServerError }, }); diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs index f366a75124..9c96fe64a4 100644 --- a/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs +++ b/src/Umbraco.Core/Services/ContentTypeEditing/ContentTypeEditingService.cs @@ -40,7 +40,12 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< UpdateTemplates(contentType, model); // save content type - await SaveAsync(contentType, userKey); + Attempt creationAttempt = await _contentTypeService.CreateAsync(contentType, userKey); + + if(creationAttempt.Success is false) + { + return Attempt.FailWithStatus(creationAttempt.Result, contentType); + } return Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType); } @@ -58,9 +63,11 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< UpdateHistoryCleanup(contentType, model); UpdateTemplates(contentType, model); - await SaveAsync(contentType, userKey); + Attempt attempt = await _contentTypeService.UpdateAsync(contentType, userKey); - return Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType); + return attempt.Success + ? Attempt.SucceedWithStatus(ContentTypeOperationStatus.Success, contentType) + : Attempt.FailWithStatus(attempt.Result, null); } public async Task> GetAvailableCompositionsAsync( @@ -93,9 +100,6 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase< contentType.SetDefaultTemplate(allowedTemplates.FirstOrDefault(t => t.Key == model.DefaultTemplateKey)); } - private async Task SaveAsync(IContentType contentType, Guid userKey) - => await _contentTypeService.SaveAsync(contentType, userKey); - protected override IContentType CreateContentType(IShortStringHelper shortStringHelper, int parentId) => new ContentType(shortStringHelper, parentId); diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs index 8340f3c584..5d91c29b27 100644 --- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs +++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs @@ -603,6 +603,75 @@ public abstract class ContentTypeServiceBase : ContentTypeSe } } + public async Task> CreateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey); + + public async Task> UpdateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey); + + private async Task> InternalSaveAsync(TItem item, Guid performingUserKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + EventMessages eventMessages = EventMessagesFactory.Get(); + + Attempt validationAttempt = ValidateCommon(item); + if (validationAttempt.Success is false) + { + return Attempt.Fail(validationAttempt.Result); + } + + SavingNotification savingNotification = GetSavingNotification(item, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + scope.Complete(); + return Attempt.Fail(ContentTypeOperationStatus.CancelledByNotification); + } + + scope.WriteLock(WriteLockIds); + + // validate the DAG transform, within the lock + ValidateLocked(item); // throws if invalid + + int userId = await _userIdKeyResolver.GetAsync(performingUserKey); + item.CreatorId = userId; + if (item.Description == string.Empty) + { + item.Description = null; + } + + Repository.Save(item); // also updates content/media/member items + + // figure out impacted content types + ContentTypeChange[] changes = ComposeContentTypeChanges(item).ToArray(); + + // Publish this in scope, see comment at GetContentTypeRefreshedNotification for more info. + await _eventAggregator.PublishAsync(GetContentTypeRefreshedNotification(changes, eventMessages)); + + scope.Notifications.Publish(GetContentTypeChangedNotification(changes, eventMessages)); + + SavedNotification savedNotification = GetSavedNotification(item, eventMessages); + savedNotification.WithStateFrom(savingNotification); + scope.Notifications.Publish(savedNotification); + + Audit(AuditType.Save, userId, item.Id); + scope.Complete(); + + return Attempt.Succeed(ContentTypeOperationStatus.Success); + } + + private Attempt ValidateCommon(TItem item) + { + if (string.IsNullOrWhiteSpace(item.Name)) + { + return Attempt.Fail(ContentTypeOperationStatus.NameCannotBeEmpty); + } + + if (item.Name.Length > 255) + { + return Attempt.Fail(ContentTypeOperationStatus.NameTooLong); + } + + return Attempt.Succeed(ContentTypeOperationStatus.Success); + } + #endregion #region Delete diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs index f3707a5059..bee084fbf9 100644 --- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs +++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs @@ -67,16 +67,31 @@ public interface IContentTypeBaseService : IContentTypeBaseService, IServ bool HasChildren(Guid id); + [Obsolete("Please use the respective Create or Update instead")] void Save(TItem? item, int userId = Constants.Security.SuperUserId); + [Obsolete("Please use the respective Create or Update instead")] Task SaveAsync(TItem item, Guid performingUserKey) { Save(item); return Task.CompletedTask; } + [Obsolete("Please use the respective Create or Update instead")] void Save(IEnumerable items, int userId = Constants.Security.SuperUserId); + Task> CreateAsync(TItem item, Guid performingUserKey) + { + Save(item); + return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success)); + } + + Task> UpdateAsync(TItem item, Guid performingUserKey) + { + Save(item); + return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success)); + } + void Delete(TItem item, int userId = Constants.Security.SuperUserId); /// diff --git a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs index 21eeedb1d5..8b52d921ee 100644 --- a/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus/ContentTypeOperationStatus.cs @@ -5,6 +5,8 @@ public enum ContentTypeOperationStatus Success, DuplicateAlias, InvalidAlias, + NameCannotBeEmpty, + NameTooLong, InvalidPropertyTypeAlias, PropertyTypeAliasCannotEqualContentTypeAlias, DuplicatePropertyTypeAlias, @@ -17,5 +19,6 @@ public enum ContentTypeOperationStatus MissingContainer, DuplicateContainer, NotFound, - NotAllowed + NotAllowed, + CancelledByNotification, }