Skip to content

Commit

Permalink
Add serializers and handlers for webhooks.
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinJump committed Apr 10, 2024
1 parent 74966ed commit 6af500a
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 2 deletions.
10 changes: 10 additions & 0 deletions uSync.BackOffice/BackOfficeConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public static class Priorites
/// RelationTypes priority
/// </summary>
public const int RelationTypes = USYNC_RESERVED_LOWER + 230;

/// <summary>
/// Webhooks priority.
/// </summary>
public const int Webhooks = USYNC_RESERVED_LOWER + 250;
}

/// <summary>
Expand Down Expand Up @@ -244,6 +249,11 @@ public static class Handlers
/// </summary>
public const string TemplateHandler = "TemplateHandler";

/// <summary>
/// WebhooksHandler name
/// </summary>
public const string WebhookHandler = "WebhookHandler";


}
}
Expand Down
73 changes: 73 additions & 0 deletions uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;

using uSync.BackOffice.Configuration;
using uSync.BackOffice.Services;
using uSync.Core;

using static Umbraco.Cms.Core.Constants;

namespace uSync.BackOffice.SyncHandlers.Handlers;

[SyncHandler(uSyncConstants.Handlers.WebhookHandler, "Webhooks", "Webhooks",
uSyncConstants.Priorites.Webhooks,
Icon = "icon-filter-arrows",
EntityType = UdiEntityType.Webhook,
IsTwoPass = false
)]
public class WebhookHandler : SyncHandlerRoot<IWebhook, IWebhook>, ISyncHandler,

Check failure on line 30 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler'

Check failure on line 30 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler'
INotificationHandler<SavedNotification<IWebhook>>,
INotificationHandler<DeletedNotification<IWebhook>>,
INotificationHandler<SavingNotification<IWebhook>>,
INotificationHandler<DeletingNotification<IWebhook>>
{
private readonly IWebhookService _webhookService;

public WebhookHandler(

Check failure on line 38 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.WebhookHandler(ILogger<SyncHandlerRoot<IWebhook, IWebhook>>, AppCaches, IShortStringHelper, SyncFileService, uSyncEventService, uSyncConfigService, ISyncItemFactory, IWebhookService)'

Check failure on line 38 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.WebhookHandler(ILogger<SyncHandlerRoot<IWebhook, IWebhook>>, AppCaches, IShortStringHelper, SyncFileService, uSyncEventService, uSyncConfigService, ISyncItemFactory, IWebhookService)'
ILogger<SyncHandlerRoot<IWebhook, IWebhook>> logger,
AppCaches appCaches,
IShortStringHelper shortStringHelper,
SyncFileService syncFileService,
uSyncEventService mutexService,
uSyncConfigService uSyncConfig,
ISyncItemFactory itemFactory,
IWebhookService webhookService)
: base(logger, appCaches, shortStringHelper, syncFileService, mutexService, uSyncConfig, itemFactory)
{
_webhookService = webhookService;
}

protected override IEnumerable<uSyncAction> DeleteMissingItems(IWebhook parent, IEnumerable<Guid> keysToKeep, bool reportOnly)

Check failure on line 52 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.DeleteMissingItems(IWebhook, IEnumerable<Guid>, bool)'

Check failure on line 52 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.DeleteMissingItems(IWebhook, IEnumerable<Guid>, bool)'
{
return [];
}

protected override IEnumerable<IWebhook> GetChildItems(IWebhook parent)

Check failure on line 57 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetChildItems(IWebhook)'

Check failure on line 57 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetChildItems(IWebhook)'
{
if (parent == null)
{
return _webhookService.GetAllAsync(0, 1000).Result.Items;
}

return [];
}

protected override IEnumerable<IWebhook> GetFolders(IWebhook parent) => [];

Check failure on line 67 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetFolders(IWebhook)'

Check failure on line 67 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetFolders(IWebhook)'

protected override IWebhook GetFromService(IWebhook item)

Check failure on line 69 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetFromService(IWebhook)'

Check failure on line 69 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetFromService(IWebhook)'
=> _webhookService.GetAsync(item.Key).Result;

protected override string GetItemName(IWebhook item) => item.Key.ToString();

Check failure on line 72 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetItemName(IWebhook)'

Check failure on line 72 in uSync.BackOffice/SyncHandlers/Handlers/WebhookHandler.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'WebhookHandler.GetItemName(IWebhook)'
}
8 changes: 7 additions & 1 deletion uSync.BackOffice/uSyncBackOfficeBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ internal static void AddHandlerNotifications(this IUmbracoBuilder builder)
builder.AddNotificationHandler<TemplateSavedNotification, TemplateHandler>();
builder.AddNotificationHandler<TemplateDeletedNotification, TemplateHandler>();

builder.AddNotificationHandler<WebhookSavedNotification, WebhookHandler>();
builder.AddNotificationHandler<WebhookDeletedNotification, WebhookHandler>();

// roots - pre-notifications for stopping things
builder
.AddNotificationHandler<ContentTypeSavingNotification, ContentTypeHandler>()
Expand Down Expand Up @@ -202,7 +205,10 @@ internal static void AddHandlerNotifications(this IUmbracoBuilder builder)
.AddNotificationHandler<MacroDeletingNotification, MacroHandler>()

.AddNotificationHandler<TemplateSavingNotification, TemplateHandler>()
.AddNotificationHandler<TemplateDeletingNotification, TemplateHandler>();
.AddNotificationHandler<TemplateDeletingNotification, TemplateHandler>()

.AddNotificationHandler<WebhookSavingNotification, WebhookHandler>()
.AddNotificationHandler<WebhookDeletingNotification, WebhookHandler>();


// content ones
Expand Down
250 changes: 250 additions & 0 deletions uSync.Core/Serialization/Serializers/WebhookSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

using Lucene.Net.Queries.Function.ValueSources;

using Microsoft.Extensions.Logging;

using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;

using uSync.Core.Models;

namespace uSync.Core.Serialization.Serializers;

[SyncSerializer("ED18C89D-A9FF-4217-9F8E-6898CA63ED81", "Webhook Serializer", uSyncConstants.Serialization.Webhook, IsTwoPass = false)]
public class WebhookSerializer : SyncSerializerBase<IWebhook>, ISyncSerializer<IWebhook>
{
private readonly IWebhookService _webhookService;

public WebhookSerializer(
IEntityService entityService,
ILogger<SyncSerializerBase<IWebhook>> logger,
IWebhookService webhookService)
: base(entityService, logger)
{
_webhookService = webhookService;
}

public override void DeleteItem(IWebhook item)
{
_webhookService.DeleteAsync(item.Key).Wait();
}

public override IWebhook FindItem(int id) => null;

public override IWebhook FindItem(Guid key)
=> _webhookService.GetAsync(key).Result;

public override IWebhook FindItem(string alias)
{
if (Guid.TryParse(alias, out Guid key))
return FindItem(key);

return null;
}

public override string ItemAlias(IWebhook item)
=> item.Key.ToString();

public override void SaveItem(IWebhook item)
{
if (item.Id > 0)
{
_webhookService.UpdateAsync(item).Wait();
}
else
{
_webhookService.CreateAsync(item).Wait();
}
}

protected override SyncAttempt<IWebhook> DeserializeCore(XElement node, SyncSerializerOptions options)
{
var key = node.GetKey();
var alias = node.GetAlias();

var details = new List<uSyncChange>();

var item = FindItem(key);
if (item == null)
{
// try and find by url/etc???
}

var url = node.Element("Url").ValueOrDefault(string.Empty);

if (item == null)
{
item = new Webhook(url);
}

if (item.Key != key)
{
details.AddUpdate("Key", item.Key, key);
item.Key = key;
}

if (item.Url != url)
{
details.AddUpdate("Url", item.Url, url);
item.Url = url;
}

details.AddRange(DeserializeContentKeys(item, node));
details.AddRange(DeserializeEvents(item, node));
details.AddRange(DeserializeHeaders(item, node));

return SyncAttempt<IWebhook>.Succeed(node.GetAlias(), item, ChangeType.Import, details);
}

private static List<uSyncChange> DeserializeContentKeys(IWebhook item, XElement node)
{
var details = new List<uSyncChange>();

var keys = node.Element("ContentTypeKeys");
if (keys == null) return details;

List<Guid> newKeys = [];

foreach (var key in keys.Elements("Key"))
{
var keyValue = key.ValueOrDefault(Guid.Empty);
if (keyValue == Guid.Empty) continue;
newKeys.Add(keyValue);
}

var newOrderedKeys = newKeys.Order().ToArray();
var existingOrderedKeys = item.ContentTypeKeys.Order().ToArray();

if (existingOrderedKeys.Equals(newOrderedKeys) is false)
{
details.AddUpdate("ContentTypeKeys",
string.Join(",", existingOrderedKeys),
string.Join(",", newOrderedKeys)
, "/");
item.ContentTypeKeys = newOrderedKeys;
}

return details;

}

private static List<uSyncChange> DeserializeEvents(IWebhook item, XElement node)
{
var details = new List<uSyncChange>();

var keys = node.Element("Events");
if (keys == null) return details;

List<string> newKeys = [];

foreach (var eventNode in keys.Elements("Event"))
{
var eventValue = eventNode.ValueOrDefault(string.Empty);
if (eventValue == string.Empty) continue;
newKeys.Add(eventValue);
}

var newOrderedEvents = newKeys.Order().ToArray();
var existingOrderedEvents = item.Events.Order().ToArray();

if (existingOrderedEvents.Equals(newOrderedEvents) is false)
{
details.AddUpdate("Events",
string.Join(",", existingOrderedEvents),
string.Join(",", newOrderedEvents)
, "/");
item.Events = newOrderedEvents;
}

return details;
}

private static List<uSyncChange> DeserializeHeaders(IWebhook item, XElement node)
{
var details = new List<uSyncChange>();

var keys = node.Element("Headers");
if (keys == null) return details;

Dictionary<string, string> newHeaders = new();

foreach (var header in keys.Elements("Header"))
{
var headerKey = header.Attribute("Key").ValueOrDefault(string.Empty);
var headerValue = header.ValueOrDefault(string.Empty);

if (headerKey == string.Empty) continue;
if (newHeaders.ContainsKey(headerKey)) continue; // stop duplicates.
newHeaders.Add(headerKey, headerValue);
}

var existingOrderedEvents = item.Headers.OrderBy(x => x.Key).ToDictionary();
var newOrderedHeaders = newHeaders.OrderBy(x => x.Key).ToDictionary();

if (existingOrderedEvents.Equals(newOrderedHeaders) is false)
{
details.AddUpdate("Events",
string.Join(",", existingOrderedEvents),
string.Join(",", newOrderedHeaders)
, "/");
item.Headers = newOrderedHeaders;
}

return details;
}



protected override SyncAttempt<XElement> SerializeCore(IWebhook item, SyncSerializerOptions options)
{
var node = InitializeBaseNode(item, item.Url);

node.Add(new XElement("Url", item.Url));
node.Add(new XElement("Enabled", item.Enabled));

node.Add(SerializeContentKeys(item));
node.Add(SerializeEvents(item));
node.Add(SerializeHeaders(item));

return SyncAttempt<XElement>.Succeed(item.Url, node, typeof(IWebhook), ChangeType.Export);

}

private static XElement SerializeContentKeys(IWebhook item)
{
var keysNode = new XElement("ContentTypeKeys");
foreach (var contentTypeKey in item.ContentTypeKeys.Order())
{
keysNode.Add(new XElement("Key", contentTypeKey));
}

return keysNode;
}

private static XElement SerializeEvents(IWebhook item)
{
var eventsNode = new XElement("Events");
foreach(var eventItem in item.Events.Order())
{
eventsNode.Add(new XElement("Event", eventItem));
}
return eventsNode;
}

private static XElement SerializeHeaders(IWebhook item)
{
var headerNode = new XElement("Headers");
foreach(var headerItem in item.Headers.OrderBy(x => x.Key))
{
headerNode.Add(new XElement("Header",
new XAttribute("Key", headerItem.Key),
new XCData(headerItem.Value)));
}

return headerNode;
}
}
2 changes: 1 addition & 1 deletion uSync.Core/Tracking/Impliment/DomainTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace uSync.Core.Tracking.Impliment
{
public class DomainTracker : SyncXmlTracker<IDomain>, ISyncTracker<IDomain>
public class DomainTracker : SyncXmlTracker<IDomain>, ISyncTracker<IDomain>
{
public DomainTracker(SyncSerializerCollection serializers)
: base(serializers)
Expand Down
Loading

0 comments on commit 6af500a

Please sign in to comment.