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 <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Nikolaj Geisle
2024-07-01 15:29:42 +02:00
committed by GitHub
parent d170193a95
commit d2707acd3b
5 changed files with 110 additions and 7 deletions

View File

@@ -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 },
});

View File

@@ -40,7 +40,12 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
UpdateTemplates(contentType, model);
// save content type
await SaveAsync(contentType, userKey);
Attempt<ContentTypeOperationStatus> creationAttempt = await _contentTypeService.CreateAsync(contentType, userKey);
if(creationAttempt.Success is false)
{
return Attempt.FailWithStatus<IContentType?, ContentTypeOperationStatus>(creationAttempt.Result, contentType);
}
return Attempt.SucceedWithStatus<IContentType?, ContentTypeOperationStatus>(ContentTypeOperationStatus.Success, contentType);
}
@@ -58,9 +63,11 @@ internal sealed class ContentTypeEditingService : ContentTypeEditingServiceBase<
UpdateHistoryCleanup(contentType, model);
UpdateTemplates(contentType, model);
await SaveAsync(contentType, userKey);
Attempt<ContentTypeOperationStatus> attempt = await _contentTypeService.UpdateAsync(contentType, userKey);
return Attempt.SucceedWithStatus<IContentType?, ContentTypeOperationStatus>(ContentTypeOperationStatus.Success, contentType);
return attempt.Success
? Attempt.SucceedWithStatus<IContentType?, ContentTypeOperationStatus>(ContentTypeOperationStatus.Success, contentType)
: Attempt.FailWithStatus<IContentType?, ContentTypeOperationStatus>(attempt.Result, null);
}
public async Task<IEnumerable<ContentTypeAvailableCompositionsResult>> 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);

View File

@@ -603,6 +603,75 @@ public abstract class ContentTypeServiceBase<TRepository, TItem> : ContentTypeSe
}
}
public async Task<Attempt<ContentTypeOperationStatus>> CreateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey);
public async Task<Attempt<ContentTypeOperationStatus>> UpdateAsync(TItem item, Guid performingUserKey) => await InternalSaveAsync(item, performingUserKey);
private async Task<Attempt<ContentTypeOperationStatus>> InternalSaveAsync(TItem item, Guid performingUserKey)
{
using ICoreScope scope = ScopeProvider.CreateCoreScope();
EventMessages eventMessages = EventMessagesFactory.Get();
Attempt<ContentTypeOperationStatus> validationAttempt = ValidateCommon(item);
if (validationAttempt.Success is false)
{
return Attempt.Fail(validationAttempt.Result);
}
SavingNotification<TItem> 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<TItem>[] 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<TItem> 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<ContentTypeOperationStatus> 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

View File

@@ -67,16 +67,31 @@ public interface IContentTypeBaseService<TItem> : 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<TItem> items, int userId = Constants.Security.SuperUserId);
Task<Attempt<ContentTypeOperationStatus>> CreateAsync(TItem item, Guid performingUserKey)
{
Save(item);
return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success));
}
Task<Attempt<ContentTypeOperationStatus>> UpdateAsync(TItem item, Guid performingUserKey)
{
Save(item);
return Task.FromResult(Attempt.Succeed(ContentTypeOperationStatus.Success));
}
void Delete(TItem item, int userId = Constants.Security.SuperUserId);
/// <summary>

View File

@@ -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,
}