Move helper methods for adding webhooks to extension methods (#15344)

* Move adding webhooks to extension methods

* Clean up WebhookEventCollectionBuilder

* Rename AddAllAvailableWebhooks to AddCmsWebhooks and internalize defaults
This commit is contained in:
Ronald Barendse
2023-12-04 10:58:11 +01:00
committed by GitHub
parent a4b4107190
commit da56f16abf
4 changed files with 298 additions and 244 deletions

View File

@@ -1,9 +1,11 @@
using Umbraco.Cms.Core.Actions;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.ContentApps;
using Umbraco.Cms.Core.Dashboards;
using Umbraco.Cms.Core.DeliveryApi;
using Umbraco.Cms.Core.DynamicRoot.Origin;
using Umbraco.Cms.Core.DynamicRoot.QuerySteps;
using Umbraco.Cms.Core.Editors;
using Umbraco.Cms.Core.HealthChecks;
using Umbraco.Cms.Core.HealthChecks.NotificationMethods;
@@ -15,8 +17,6 @@ using Umbraco.Cms.Core.PropertyEditors.Validators;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Sections;
using Umbraco.Cms.Core.Snippets;
using Umbraco.Cms.Core.DynamicRoot.QuerySteps;
using Umbraco.Cms.Core.DynamicRoot.Origin;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Core.Tour;
using Umbraco.Cms.Core.Trees;
@@ -145,7 +145,7 @@ public static partial class UmbracoBuilderExtensions
builder.FilterHandlers().Add(() => builder.TypeLoader.GetTypes<IFilterHandler>());
builder.SortHandlers().Add(() => builder.TypeLoader.GetTypes<ISortHandler>());
builder.ContentIndexHandlers().Add(() => builder.TypeLoader.GetTypes<IContentIndexHandler>());
builder.WebhookEvents().AddCoreWebhooks();
builder.WebhookEvents().AddDefaultWebhooks();
}
/// <summary>

View File

@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using Umbraco.Cms.Core;
@@ -132,9 +133,7 @@ public static class TypeExtensions
/// <c>true</c> if [is of generic type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static bool IsOfGenericType(this Type type, Type genericType)
{
return type.TryGetGenericArguments(genericType, out Type[]? args);
}
=> type.TryGetGenericArguments(genericType, out _);
/// <summary>
/// Will find the generic type of the 'type' parameter passed in that is equal to the 'genericType' parameter passed in
@@ -143,17 +142,10 @@ public static class TypeExtensions
/// <param name="genericType"></param>
/// <param name="genericArgType"></param>
/// <returns></returns>
public static bool TryGetGenericArguments(this Type type, Type genericType, out Type[]? genericArgType)
public static bool TryGetGenericArguments(this Type type, Type genericType, [NotNullWhen(true)] out Type[]? genericArgType)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
if (genericType == null)
{
throw new ArgumentNullException("genericType");
}
ArgumentNullException.ThrowIfNull(type);
ArgumentNullException.ThrowIfNull(genericType);
if (genericType.IsGenericType == false)
{

View File

@@ -1,38 +1,20 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Webhooks.Events.Content;
using Umbraco.Cms.Core.Webhooks.Events.DataType;
using Umbraco.Cms.Core.Webhooks.Events.Dictionary;
using Umbraco.Cms.Core.Webhooks.Events.Domain;
using Umbraco.Cms.Core.Webhooks.Events.Language;
using Umbraco.Cms.Core.Webhooks.Events.Media;
using Umbraco.Cms.Core.Webhooks.Events.MediaType;
using Umbraco.Cms.Core.Webhooks.Events.Member;
using Umbraco.Cms.Core.Webhooks.Events.MemberType;
using Umbraco.Cms.Core.Webhooks.Events.Package;
using Umbraco.Cms.Core.Webhooks.Events.PublicAccess;
using Umbraco.Cms.Core.Webhooks.Events.Relation;
using Umbraco.Cms.Core.Webhooks.Events.RelationType;
using Umbraco.Cms.Core.Webhooks.Events.Script;
using Umbraco.Cms.Core.Webhooks.Events.Stylesheet;
using Umbraco.Cms.Core.Webhooks.Events.Template;
using Umbraco.Cms.Core.Webhooks.Events.User;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Webhooks;
public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase<WebhookEventCollectionBuilder,
WebhookEventCollection, IWebhookEvent>
public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase<WebhookEventCollectionBuilder, WebhookEventCollection, IWebhookEvent>
{
protected override WebhookEventCollectionBuilder This => this;
public override void RegisterWith(IServiceCollection services)
{
// register the collection
services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection,
ServiceLifetime.Singleton));
services.Add(new ServiceDescriptor(typeof(WebhookEventCollection), CreateCollection, ServiceLifetime.Singleton));
// register the types
RegisterTypes(services);
@@ -43,16 +25,16 @@ public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase<Webhoo
{
Type[] types = GetRegisteringTypes(GetTypes()).ToArray();
// ensure they are safe
// Ensure they are safe
foreach (Type type in types)
{
EnsureType(type, "register");
}
// Register all webhooks as notification handlers
foreach (Type type in types)
{
Type? notificationType = GetNotificationType(type);
if (notificationType is null)
{
continue;
@@ -63,211 +45,12 @@ public class WebhookEventCollectionBuilder : OrderedCollectionBuilderBase<Webhoo
type,
ServiceLifetime.Transient);
if (!services.Contains(descriptor))
{
services.Add(descriptor);
}
services.TryAddEnumerable(descriptor);
}
}
private Type? GetNotificationType(Type handlerType)
{
if (handlerType.IsOfGenericType(typeof(INotificationAsyncHandler<>)))
{
Type[] genericArguments = handlerType.BaseType!.GetGenericArguments();
Type? notificationType =
genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg));
if (notificationType is not null)
{
return notificationType;
}
}
return null;
}
public WebhookEventCollectionBuilder AddAllAvailableWebhooks() =>
this.AddContentWebhooks()
.AddDataTypeWebhooks()
.AddDictionaryWebhooks()
.AddDomainWebhooks()
.AddLanguageWebhooks()
.AddMediaWebhooks()
.AddMemberWebhooks()
.AddMemberTypeWebhooks()
.AddPackageWebhooks()
.AddPublicAccessWebhooks()
.AddRelationWebhooks()
.AddScriptWebhooks()
.AddStylesheetWebhooks()
.AddTemplateWebhooks()
.AddUserWebhooks();
public WebhookEventCollectionBuilder AddContentWebhooks()
{
Append<ContentCopiedWebhookEvent>();
Append<ContentDeletedBlueprintWebhookEvent>();
Append<ContentDeletedVersionsWebhookEvent>();
Append<ContentDeletedWebhookEvent>();
Append<ContentEmptiedRecycleBinWebhookEvent>();
Append<ContentMovedToRecycleBinWebhookEvent>();
Append<ContentMovedWebhookEvent>();
Append<ContentPublishedWebhookEvent>();
Append<ContentRolledBackWebhookEvent>();
Append<ContentSavedBlueprintWebhookEvent>();
Append<ContentSavedWebhookEvent>();
Append<ContentSortedWebhookEvent>();
Append<ContentUnpublishedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddCoreWebhooks()
{
Append<ContentDeletedWebhookEvent>();
Append<ContentPublishedWebhookEvent>();
Append<ContentUnpublishedWebhookEvent>();
Append<MediaDeletedWebhookEvent>();
Append<MediaSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddDataTypeWebhooks()
{
Append<DataTypeDeletedWebhookEvent>();
Append<DataTypeMovedWebhookEvent>();
Append<DataTypeSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddDictionaryWebhooks()
{
Append<DictionaryItemDeletedWebhookEvent>();
Append<DictionaryItemSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddDomainWebhooks()
{
Append<DomainDeletedWebhookEvent>();
Append<DomainSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddLanguageWebhooks()
{
Append<LanguageDeletedWebhookEvent>();
Append<LanguageSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddMediaWebhooks()
{
// Even though these two are in the AddCoreWebhooks()
// The job of the CollectionBuilder should be removing duplicates
// Would allow someone to use .AddCoreWebhooks().AddMediaWebhooks()
// Or if they explicitly they could skip over CoreWebHooks and just add this perhaps
Append<MediaDeletedWebhookEvent>();
Append<MediaSavedWebhookEvent>();
Append<MediaEmptiedRecycleBinWebhookEvent>();
Append<MediaMovedWebhookEvent>();
Append<MediaMovedToRecycleBinWebhookEvent>();
Append<MediaTypeChangedWebhookEvent>();
Append<MediaTypeDeletedWebhookEvent>();
Append<MediaTypeMovedWebhookEvent>();
Append<MediaTypeSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddMemberWebhooks()
{
Append<AssignedMemberRolesWebhookEvent>();
Append<ExportedMemberWebhookEvent>();
Append<MemberDeletedWebhookEvent>();
Append<MemberGroupDeletedWebhookEvent>();
Append<MemberGroupSavedWebhookEvent>();
Append<MemberSavedWebhookEvent>();
Append<RemovedMemberRolesWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddMemberTypeWebhooks()
{
Append<MemberTypeChangedWebhookEvent>();
Append<MemberTypeDeletedWebhookEvent>();
Append<MemberTypeMovedWebhookEvent>();
Append<MemberTypeSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddPackageWebhooks()
{
Append<ImportedPackageWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddPublicAccessWebhooks()
{
Append<PublicAccessEntryDeletedWebhookEvent>();
Append<PublicAccessEntrySavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddRelationWebhooks()
{
Append<RelationDeletedWebhookEvent>();
Append<RelationSavedWebhookEvent>();
Append<RelationTypeDeletedWebhookEvent>();
Append<RelationTypeSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddScriptWebhooks()
{
Append<ScriptDeletedWebhookEvent>();
Append<ScriptSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddStylesheetWebhooks()
{
Append<StylesheetDeletedWebhookEvent>();
Append<StylesheetSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddTemplateWebhooks()
{
Append<PartialViewDeletedWebhookEvent>();
Append<PartialViewSavedWebhookEvent>();
Append<TemplateDeletedWebhookEvent>();
Append<TemplateSavedWebhookEvent>();
return this;
}
public WebhookEventCollectionBuilder AddUserWebhooks()
{
Append<AssignedUserGroupPermissionsWebhookEvent>();
Append<UserDeletedWebhookEvent>();
Append<UserForgotPasswordRequestedWebhookEvent>();
Append<UserForgottenPasswordRequestedWebhookEvent>();
Append<UserGroupDeletedWebhookEvent>();
Append<UserGroupSavedWebhookEvent>();
Append<UserLockedWebhookEvent>();
Append<UserLoginFailedWebhookEvent>();
Append<UserLoginRequiresVerificationWebhookEvent>();
Append<UserLoginSuccessWebhookEvent>();
Append<UserLogoutSuccessWebhookEvent>();
Append<UserPasswordChangedWebhookEvent>();
Append<UserPasswordResetWebhookEvent>();
Append<UserSavedWebhookEvent>();
Append<UserTwoFactorRequestedWebhookEvent>();
Append<UserUnlockedWebhookEvent>();
return this;
}
=> handlerType.TryGetGenericArguments(typeof(INotificationAsyncHandler<>), out Type[]? genericArguments)
? genericArguments.FirstOrDefault(arg => typeof(INotification).IsAssignableFrom(arg))
: null;
}

View File

@@ -0,0 +1,279 @@
using Umbraco.Cms.Core.Webhooks;
using Umbraco.Cms.Core.Webhooks.Events.Content;
using Umbraco.Cms.Core.Webhooks.Events.DataType;
using Umbraco.Cms.Core.Webhooks.Events.Dictionary;
using Umbraco.Cms.Core.Webhooks.Events.Domain;
using Umbraco.Cms.Core.Webhooks.Events.Language;
using Umbraco.Cms.Core.Webhooks.Events.Media;
using Umbraco.Cms.Core.Webhooks.Events.MediaType;
using Umbraco.Cms.Core.Webhooks.Events.Member;
using Umbraco.Cms.Core.Webhooks.Events.MemberType;
using Umbraco.Cms.Core.Webhooks.Events.Package;
using Umbraco.Cms.Core.Webhooks.Events.PublicAccess;
using Umbraco.Cms.Core.Webhooks.Events.Relation;
using Umbraco.Cms.Core.Webhooks.Events.RelationType;
using Umbraco.Cms.Core.Webhooks.Events.Script;
using Umbraco.Cms.Core.Webhooks.Events.Stylesheet;
using Umbraco.Cms.Core.Webhooks.Events.Template;
using Umbraco.Cms.Core.Webhooks.Events.User;
namespace Umbraco.Cms.Core.DependencyInjection;
public static class WebhookEventCollectionBuilderExtensions
{
internal static WebhookEventCollectionBuilder AddDefaultWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<ContentDeletedWebhookEvent>()
.Append<ContentPublishedWebhookEvent>()
.Append<ContentUnpublishedWebhookEvent>()
.Append<MediaDeletedWebhookEvent>()
.Append<MediaSavedWebhookEvent>();
/// <summary>
/// Adds all available CMS webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddCmsWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.AddContentWebhooks()
.AddDataTypeWebhooks()
.AddDictionaryWebhooks()
.AddDomainWebhooks()
.AddLanguageWebhooks()
.AddMediaWebhooks()
.AddMemberWebhooks()
.AddMemberTypeWebhooks()
.AddPackageWebhooks()
.AddPublicAccessWebhooks()
.AddRelationWebhooks()
.AddScriptWebhooks()
.AddStylesheetWebhooks()
.AddTemplateWebhooks()
.AddUserWebhooks();
/// <summary>
/// Adds the content webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddContentWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<ContentCopiedWebhookEvent>()
.Append<ContentDeletedBlueprintWebhookEvent>()
.Append<ContentDeletedVersionsWebhookEvent>()
.Append<ContentDeletedWebhookEvent>()
.Append<ContentEmptiedRecycleBinWebhookEvent>()
.Append<ContentMovedToRecycleBinWebhookEvent>()
.Append<ContentMovedWebhookEvent>()
.Append<ContentPublishedWebhookEvent>()
.Append<ContentRolledBackWebhookEvent>()
.Append<ContentSavedBlueprintWebhookEvent>()
.Append<ContentSavedWebhookEvent>()
.Append<ContentSortedWebhookEvent>()
.Append<ContentUnpublishedWebhookEvent>();
/// <summary>
/// Adds the data type webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddDataTypeWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<DataTypeDeletedWebhookEvent>()
.Append<DataTypeMovedWebhookEvent>()
.Append<DataTypeSavedWebhookEvent>();
/// <summary>
/// Adds the dictionary webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddDictionaryWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<DictionaryItemDeletedWebhookEvent>()
.Append<DictionaryItemSavedWebhookEvent>();
/// <summary>
/// Adds the domain webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddDomainWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<DomainDeletedWebhookEvent>()
.Append<DomainSavedWebhookEvent>();
/// <summary>
/// Adds the language webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddLanguageWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<LanguageDeletedWebhookEvent>()
.Append<LanguageSavedWebhookEvent>();
/// <summary>
/// Adds the media webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddMediaWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<MediaDeletedWebhookEvent>()
.Append<MediaSavedWebhookEvent>()
.Append<MediaEmptiedRecycleBinWebhookEvent>()
.Append<MediaMovedWebhookEvent>()
.Append<MediaMovedToRecycleBinWebhookEvent>()
.Append<MediaTypeChangedWebhookEvent>()
.Append<MediaTypeDeletedWebhookEvent>()
.Append<MediaTypeMovedWebhookEvent>()
.Append<MediaTypeSavedWebhookEvent>();
/// <summary>
/// Adds the member webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddMemberWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<AssignedMemberRolesWebhookEvent>()
.Append<ExportedMemberWebhookEvent>()
.Append<MemberDeletedWebhookEvent>()
.Append<MemberGroupDeletedWebhookEvent>()
.Append<MemberGroupSavedWebhookEvent>()
.Append<MemberSavedWebhookEvent>()
.Append<RemovedMemberRolesWebhookEvent>();
/// <summary>
/// Adds the member type webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddMemberTypeWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<MemberTypeChangedWebhookEvent>()
.Append<MemberTypeDeletedWebhookEvent>()
.Append<MemberTypeMovedWebhookEvent>()
.Append<MemberTypeSavedWebhookEvent>();
/// <summary>
/// Adds the package webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddPackageWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<ImportedPackageWebhookEvent>();
/// <summary>
/// Adds the public access webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddPublicAccessWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<PublicAccessEntryDeletedWebhookEvent>()
.Append<PublicAccessEntrySavedWebhookEvent>();
/// <summary>
/// Adds the relation webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddRelationWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<RelationDeletedWebhookEvent>()
.Append<RelationSavedWebhookEvent>()
.Append<RelationTypeDeletedWebhookEvent>()
.Append<RelationTypeSavedWebhookEvent>();
/// <summary>
/// Adds the script webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddScriptWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<ScriptDeletedWebhookEvent>()
.Append<ScriptSavedWebhookEvent>();
/// <summary>
/// Adds the stylesheet webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddStylesheetWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<StylesheetDeletedWebhookEvent>()
.Append<StylesheetSavedWebhookEvent>();
/// <summary>
/// Adds the template webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddTemplateWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<PartialViewDeletedWebhookEvent>()
.Append<PartialViewSavedWebhookEvent>()
.Append<TemplateDeletedWebhookEvent>()
.Append<TemplateSavedWebhookEvent>();
/// <summary>
/// Adds the user webhook events.
/// </summary>
/// <param name="builder">The builder.</param>
/// <returns>
/// The builder.
/// </returns>
public static WebhookEventCollectionBuilder AddUserWebhooks(this WebhookEventCollectionBuilder builder)
=> builder
.Append<AssignedUserGroupPermissionsWebhookEvent>()
.Append<UserDeletedWebhookEvent>()
.Append<UserForgotPasswordRequestedWebhookEvent>()
.Append<UserForgottenPasswordRequestedWebhookEvent>()
.Append<UserGroupDeletedWebhookEvent>()
.Append<UserGroupSavedWebhookEvent>()
.Append<UserLockedWebhookEvent>()
.Append<UserLoginFailedWebhookEvent>()
.Append<UserLoginRequiresVerificationWebhookEvent>()
.Append<UserLoginSuccessWebhookEvent>()
.Append<UserLogoutSuccessWebhookEvent>()
.Append<UserPasswordChangedWebhookEvent>()
.Append<UserPasswordResetWebhookEvent>()
.Append<UserSavedWebhookEvent>()
.Append<UserTwoFactorRequestedWebhookEvent>()
.Append<UserUnlockedWebhookEvent>();
}