mirror of https://github.com/Squidex/squidex.git
33 changed files with 1251 additions and 0 deletions
@ -0,0 +1,26 @@ |
|||||
|
// ==========================================================================
|
||||
|
// WebhookAction.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Actions |
||||
|
{ |
||||
|
[TypeName(nameof(WebhookAction))] |
||||
|
public sealed class WebhookAction : RuleAction |
||||
|
{ |
||||
|
public Uri Url { get; set; } |
||||
|
|
||||
|
public string SharedSecret { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleActionVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IActionVisitor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public interface IRuleActionVisitor<T> |
||||
|
{ |
||||
|
T Visit(WebhookAction action); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleTriggerVisitor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public interface IRuleTriggerVisitor<T> |
||||
|
{ |
||||
|
T Visit(ContentChangedTrigger trigger); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Rule.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public sealed class Rule |
||||
|
{ |
||||
|
private RuleTrigger trigger; |
||||
|
private RuleAction action; |
||||
|
private bool isEnabled = true; |
||||
|
|
||||
|
public RuleTrigger Trigger |
||||
|
{ |
||||
|
get { return trigger; } |
||||
|
} |
||||
|
|
||||
|
public RuleAction Action |
||||
|
{ |
||||
|
get { return action; } |
||||
|
} |
||||
|
|
||||
|
public bool IsEnabled |
||||
|
{ |
||||
|
get { return isEnabled; } |
||||
|
} |
||||
|
|
||||
|
public Rule(RuleTrigger trigger, RuleAction action) |
||||
|
{ |
||||
|
Guard.NotNull(trigger, nameof(trigger)); |
||||
|
Guard.NotNull(action, nameof(action)); |
||||
|
|
||||
|
this.trigger = trigger; |
||||
|
this.action = action; |
||||
|
} |
||||
|
|
||||
|
public void Enable() |
||||
|
{ |
||||
|
this.isEnabled = true; |
||||
|
} |
||||
|
|
||||
|
public void Disable() |
||||
|
{ |
||||
|
this.isEnabled = false; |
||||
|
} |
||||
|
|
||||
|
public void Update(RuleTrigger newTrigger) |
||||
|
{ |
||||
|
Guard.NotNull(newTrigger, nameof(newTrigger)); |
||||
|
|
||||
|
if (newTrigger.GetType() != trigger.GetType()) |
||||
|
{ |
||||
|
throw new ArgumentException("New trigger has another type.", nameof(newTrigger)); |
||||
|
} |
||||
|
|
||||
|
trigger = newTrigger; |
||||
|
} |
||||
|
|
||||
|
public void Update(RuleAction newAction) |
||||
|
{ |
||||
|
Guard.NotNull(newAction, nameof(newAction)); |
||||
|
|
||||
|
if (newAction.GetType() != trigger.GetType()) |
||||
|
{ |
||||
|
throw new ArgumentException("New action has another type.", nameof(newAction)); |
||||
|
} |
||||
|
|
||||
|
action = newAction; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleAction.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public abstract class RuleAction |
||||
|
{ |
||||
|
public abstract T Accept<T>(IRuleActionVisitor<T> visitor); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleJob.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using NodaTime; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public sealed class RuleJob |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
public Guid AppId { get; set; } |
||||
|
|
||||
|
public string EventName { get; set; } |
||||
|
|
||||
|
public string ActionName { get; set; } |
||||
|
|
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
public Instant Created { get; set; } |
||||
|
|
||||
|
public Instant Expires { get; set; } |
||||
|
|
||||
|
public RuleJobData ActionData { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleJobData.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public sealed class RuleJobData : Dictionary<string, JToken> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTrigger.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules |
||||
|
{ |
||||
|
public abstract class RuleTrigger |
||||
|
{ |
||||
|
public abstract T Accept<T>(IRuleTriggerVisitor<T> visitor); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedTrigger.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Triggers |
||||
|
{ |
||||
|
[TypeName(nameof(ContentChangedTrigger))] |
||||
|
public sealed class ContentChangedTrigger : RuleTrigger |
||||
|
{ |
||||
|
public List<ContentChangedTriggerSchema> Schemas { get; set; } |
||||
|
|
||||
|
public override T Accept<T>(IRuleTriggerVisitor<T> visitor) |
||||
|
{ |
||||
|
return visitor.Visit(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedTriggerSchema.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Rules.Triggers |
||||
|
{ |
||||
|
public sealed class ContentChangedTriggerSchema |
||||
|
{ |
||||
|
public Guid SchemaId { get; set; } |
||||
|
|
||||
|
public bool SendCreate { get; set; } |
||||
|
|
||||
|
public bool SendUpdate { get; set; } |
||||
|
|
||||
|
public bool SendDelete { get; set; } |
||||
|
|
||||
|
public bool SendPublish { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
// ==========================================================================
|
||||
|
// WebhookActionHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Net.Http; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Http; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.ActionHandlers |
||||
|
{ |
||||
|
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction> |
||||
|
{ |
||||
|
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
||||
|
|
||||
|
private readonly JsonSerializer serializer; |
||||
|
|
||||
|
public WebhookActionHandler(JsonSerializer serializer) |
||||
|
{ |
||||
|
Guard.NotNull(serializer, nameof(serializer)); |
||||
|
|
||||
|
this.serializer = serializer; |
||||
|
} |
||||
|
|
||||
|
protected override (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, WebhookAction action) |
||||
|
{ |
||||
|
var body = CreatePayload(@event, eventName); |
||||
|
|
||||
|
var signature = $"{body.ToString(Formatting.Indented)}{action.SharedSecret}".Sha256Base64(); |
||||
|
|
||||
|
var ruleDescription = $"Send event to webhook {action.Url}"; |
||||
|
var ruleData = new RuleJobData |
||||
|
{ |
||||
|
["RequestUrl"] = action.Url, |
||||
|
["RequestBody"] = body, |
||||
|
["RequestSignature"] = signature |
||||
|
}; |
||||
|
|
||||
|
return (ruleDescription, ruleData); |
||||
|
} |
||||
|
|
||||
|
private JObject CreatePayload(Envelope<AppEvent> @event, string eventName) |
||||
|
{ |
||||
|
return new JObject( |
||||
|
new JProperty("type", eventName), |
||||
|
new JProperty("payload", JObject.FromObject(@event.Payload, serializer)), |
||||
|
new JProperty("timestamp", @event.Headers.Timestamp().ToString())); |
||||
|
} |
||||
|
|
||||
|
public override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job) |
||||
|
{ |
||||
|
var requestBody = job["RequestBody"].ToString(Formatting.Indented); |
||||
|
var request = BuildRequest(job, requestBody); |
||||
|
|
||||
|
HttpResponseMessage response = null; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using (var client = new HttpClient { Timeout = Timeout }) |
||||
|
{ |
||||
|
response = await client.SendAsync(request); |
||||
|
|
||||
|
var responseString = await response.Content.ReadAsStringAsync(); |
||||
|
var requestDump = DumpFormatter.BuildDump(request, response, requestBody, responseString, TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, null); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
if (request != null) |
||||
|
{ |
||||
|
var requestDump = DumpFormatter.BuildDump(request, response, requestBody, ex.ToString(), TimeSpan.Zero, false); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var requestDump = ex.ToString(); |
||||
|
|
||||
|
return (requestDump, ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static HttpRequestMessage BuildRequest(Dictionary<string, JToken> job, string requestBody) |
||||
|
{ |
||||
|
var requestUrl = job["RequestUrl"].ToString(); |
||||
|
var requestSignature = job["RequestSignature"].ToString(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Post, requestUrl) |
||||
|
{ |
||||
|
Content = new StringContent(requestBody, Encoding.UTF8, "application/json") |
||||
|
}; |
||||
|
|
||||
|
request.Headers.Add("X-Signature", requestSignature); |
||||
|
request.Headers.Add("User-Agent", "Squidex Webhook"); |
||||
|
|
||||
|
return request; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleActionHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public interface IRuleActionHandler |
||||
|
{ |
||||
|
Type ActionType { get; } |
||||
|
|
||||
|
(string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, RuleAction action); |
||||
|
|
||||
|
Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData data); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleTriggerHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public interface IRuleTriggerHandler |
||||
|
{ |
||||
|
Type TriggerType { get; } |
||||
|
|
||||
|
bool Triggers(Envelope<AppEvent> @event, RuleTrigger trigger); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleActionHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public abstract class RuleActionHandler<T> : IRuleActionHandler where T : RuleAction |
||||
|
{ |
||||
|
Type IRuleActionHandler.ActionType |
||||
|
{ |
||||
|
get { return typeof(T); } |
||||
|
} |
||||
|
|
||||
|
(string Description, RuleJobData Data) IRuleActionHandler.CreateJob(Envelope<AppEvent> @event, string eventName, RuleAction action) |
||||
|
{ |
||||
|
return CreateJob(@event, eventName, (T)action); |
||||
|
} |
||||
|
|
||||
|
protected abstract (string Description, RuleJobData Data) CreateJob(Envelope<AppEvent> @event, string eventName, T action); |
||||
|
|
||||
|
public abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(RuleJobData job); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public enum RuleResult |
||||
|
{ |
||||
|
Pending, |
||||
|
Success, |
||||
|
Failed, |
||||
|
Timeout |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,159 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleService.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public sealed class RuleService |
||||
|
{ |
||||
|
private const string ContentPrefix = "Content"; |
||||
|
private static readonly Duration ExpirationTime = Duration.FromDays(2); |
||||
|
private readonly Dictionary<Type, IRuleActionHandler> ruleActionHandlers; |
||||
|
private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers; |
||||
|
private readonly TypeNameRegistry typeNameRegistry; |
||||
|
private readonly IClock clock; |
||||
|
|
||||
|
public RuleService( |
||||
|
IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers, |
||||
|
IEnumerable<IRuleActionHandler> ruleActionHandlers, |
||||
|
IClock clock, |
||||
|
TypeNameRegistry typeNameRegistry) |
||||
|
{ |
||||
|
Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers)); |
||||
|
Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers)); |
||||
|
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); |
||||
|
Guard.NotNull(clock, nameof(clock)); |
||||
|
|
||||
|
this.typeNameRegistry = typeNameRegistry; |
||||
|
|
||||
|
this.ruleTriggerHandlers = ruleTriggerHandlers.ToDictionary(x => x.TriggerType); |
||||
|
this.ruleActionHandlers = ruleActionHandlers.ToDictionary(x => x.ActionType); |
||||
|
|
||||
|
this.clock = clock; |
||||
|
} |
||||
|
|
||||
|
public RuleJob CreateJob(Rule rule, Envelope<IEvent> @event) |
||||
|
{ |
||||
|
Guard.NotNull(rule, nameof(rule)); |
||||
|
Guard.NotNull(@event, nameof(@event)); |
||||
|
|
||||
|
if (!(@event.Payload is AppEvent appEvent)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var actionType = rule.Action.GetType(); |
||||
|
|
||||
|
if (!ruleTriggerHandlers.TryGetValue(rule.Trigger.GetType(), out var triggerHandler)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (!ruleActionHandlers.TryGetValue(actionType, out var actionHandler)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var appEventEnvelope = @event.To<AppEvent>(); |
||||
|
|
||||
|
if (!triggerHandler.Triggers(appEventEnvelope, rule.Trigger)) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
var eventName = CreateEventName(appEvent); |
||||
|
|
||||
|
var now = clock.GetCurrentInstant(); |
||||
|
|
||||
|
var actionName = typeNameRegistry.GetName(actionType); |
||||
|
var actionData = actionHandler.CreateJob(appEventEnvelope, eventName, rule.Action); |
||||
|
|
||||
|
var job = new RuleJob |
||||
|
{ |
||||
|
Id = Guid.NewGuid(), |
||||
|
ActionName = actionName, |
||||
|
ActionData = actionData.Data, |
||||
|
AppId = appEvent.AppId.Id, |
||||
|
Created = now, |
||||
|
EventName = eventName, |
||||
|
Expires = now.Plus(ExpirationTime), |
||||
|
Description = actionData.Description |
||||
|
}; |
||||
|
|
||||
|
return job; |
||||
|
} |
||||
|
|
||||
|
public async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, Dictionary<string, JToken> job) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var actionType = typeNameRegistry.GetType(actionName); |
||||
|
var actionWatch = Stopwatch.StartNew(); |
||||
|
|
||||
|
var result = await ruleActionHandlers[actionType].ExecuteJobAsync(job); |
||||
|
|
||||
|
actionWatch.Stop(); |
||||
|
|
||||
|
var dumpBuilder = new StringBuilder(result.Dump); |
||||
|
|
||||
|
dumpBuilder.AppendLine(); |
||||
|
dumpBuilder.AppendFormat("Elapesed {0}.", actionWatch.Elapsed); |
||||
|
dumpBuilder.AppendLine(); |
||||
|
|
||||
|
if (result.Exception is TimeoutException || result.Exception is OperationCanceledException) |
||||
|
{ |
||||
|
dumpBuilder.AppendLine(); |
||||
|
dumpBuilder.AppendLine("Action timed out."); |
||||
|
|
||||
|
return (dumpBuilder.ToString(), RuleResult.Timeout, actionWatch.Elapsed); |
||||
|
} |
||||
|
else if (result.Exception != null) |
||||
|
{ |
||||
|
return (dumpBuilder.ToString(), RuleResult.Failed, actionWatch.Elapsed); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return (dumpBuilder.ToString(), RuleResult.Success, actionWatch.Elapsed); |
||||
|
} |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
return (ex.ToString(), RuleResult.Failed, TimeSpan.Zero); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private string CreateEventName(AppEvent appEvent) |
||||
|
{ |
||||
|
var eventName = typeNameRegistry.GetName(appEvent.GetType()); |
||||
|
|
||||
|
if (appEvent is SchemaEvent schemaEvent) |
||||
|
{ |
||||
|
if (eventName.StartsWith(ContentPrefix, StringComparison.Ordinal)) |
||||
|
{ |
||||
|
eventName = eventName.Substring(ContentPrefix.Length); |
||||
|
} |
||||
|
|
||||
|
return $"{schemaEvent.SchemaId.Name.ToPascalCase()}{eventName}"; |
||||
|
} |
||||
|
|
||||
|
return eventName; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTriggerHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules |
||||
|
{ |
||||
|
public abstract class RuleTriggerHandler<T> : IRuleTriggerHandler where T : RuleTrigger |
||||
|
{ |
||||
|
public Type TriggerType |
||||
|
{ |
||||
|
get { return typeof(T); } |
||||
|
} |
||||
|
|
||||
|
bool IRuleTriggerHandler.Triggers(Envelope<AppEvent> @event, RuleTrigger trigger) |
||||
|
{ |
||||
|
return Triggers(@event, (T)trigger); |
||||
|
} |
||||
|
|
||||
|
protected abstract bool Triggers(Envelope<AppEvent> @event, T trigger); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedTriggerHandler.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Events.Contents; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.HandleRules.Triggers |
||||
|
{ |
||||
|
public sealed class ContentChangedTriggerHandler : RuleTriggerHandler<ContentChangedTrigger> |
||||
|
{ |
||||
|
protected override bool Triggers(Envelope<AppEvent> @event, ContentChangedTrigger trigger) |
||||
|
{ |
||||
|
if (trigger.Schemas != null && @event.Payload is SchemaEvent schemaEvent) |
||||
|
{ |
||||
|
foreach (var schema in trigger.Schemas) |
||||
|
{ |
||||
|
if (MatchsSchema(schema, schemaEvent) && MatchsType(schema, schemaEvent)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private static bool MatchsSchema(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event) |
||||
|
{ |
||||
|
return @event.SchemaId.Id == webhookSchema.SchemaId; |
||||
|
} |
||||
|
|
||||
|
private static bool MatchsType(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event) |
||||
|
{ |
||||
|
return |
||||
|
(webhookSchema.SendCreate && @event is ContentCreated) || |
||||
|
(webhookSchema.SendUpdate && @event is ContentUpdated) || |
||||
|
(webhookSchema.SendDelete && @event is ContentDeleted) || |
||||
|
(webhookSchema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleCreated.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
[EventType(nameof(RuleCreated))] |
||||
|
public sealed class RuleCreated : RuleEvent |
||||
|
{ |
||||
|
public RuleTrigger Trigger { get; set; } |
||||
|
|
||||
|
public RuleAction Action { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDeleted.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
[EventType(nameof(RuleDeleted))] |
||||
|
public sealed class RuleDeleted : RuleEvent |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDisabled.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
[EventType(nameof(RuleDisabled))] |
||||
|
public sealed class RuleDisabled : RuleEvent |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEnabled.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
[EventType(nameof(RuleEnabled))] |
||||
|
public sealed class RuleEnabled : RuleEvent |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEvent.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
public abstract class RuleEvent : AppEvent |
||||
|
{ |
||||
|
public Guid RuleId { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleUpdated.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules |
||||
|
{ |
||||
|
[EventType(nameof(RuleUpdated))] |
||||
|
public sealed class RuleUpdated : RuleEvent |
||||
|
{ |
||||
|
public RuleTrigger Trigger { get; set; } |
||||
|
|
||||
|
public RuleAction Action { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEventDispatcher.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Events.Webhooks; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Events.Rules.Utils |
||||
|
{ |
||||
|
public static class RuleEventDispatcher |
||||
|
{ |
||||
|
public static Rule Create(RuleCreated @event) |
||||
|
{ |
||||
|
return new Rule(@event.Trigger, @event.Action); |
||||
|
} |
||||
|
|
||||
|
public static Rule Create(WebhookCreated @event) |
||||
|
{ |
||||
|
return new Rule(CreateTrigger(@event), CreateAction(@event)); |
||||
|
} |
||||
|
|
||||
|
public static void Apply(this Rule rule, WebhookUpdated @event) |
||||
|
{ |
||||
|
rule.Update(CreateTrigger(@event)); |
||||
|
|
||||
|
if (rule.Action is WebhookAction webhookAction) |
||||
|
{ |
||||
|
webhookAction.Url = @event.Url; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void Apply(this Rule rule, RuleUpdated @event) |
||||
|
{ |
||||
|
if (@event.Trigger != null) |
||||
|
{ |
||||
|
rule.Update(@event.Trigger); |
||||
|
} |
||||
|
|
||||
|
if (@event.Action != null) |
||||
|
{ |
||||
|
rule.Update(@event.Action); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void Apply(this Rule rule, RuleEnabled @event) |
||||
|
{ |
||||
|
rule.Enable(); |
||||
|
} |
||||
|
|
||||
|
public static void Apply(this Rule rule, RuleDisabled @event) |
||||
|
{ |
||||
|
rule.Disable(); |
||||
|
} |
||||
|
|
||||
|
private static WebhookAction CreateAction(WebhookCreated @event) |
||||
|
{ |
||||
|
var action = new WebhookAction { Url = @event.Url, SharedSecret = @event.SharedSecret }; |
||||
|
|
||||
|
return action; |
||||
|
} |
||||
|
|
||||
|
private static ContentChangedTrigger CreateTrigger(WebhookEditEvent @event) |
||||
|
{ |
||||
|
var trigger = new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = @event.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToList() |
||||
|
}; |
||||
|
|
||||
|
return trigger; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public interface IRuleEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion |
||||
|
{ |
||||
|
Rule Rule { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEventEntity.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public interface IRuleEventEntity : IEntity |
||||
|
{ |
||||
|
RuleJob Job { get; } |
||||
|
|
||||
|
Instant? NextAttempt { get; } |
||||
|
|
||||
|
RuleJobResult JobResult { get; } |
||||
|
|
||||
|
RuleResult Result { get; } |
||||
|
|
||||
|
int NumCalls { get; } |
||||
|
|
||||
|
string LastDump { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleEventRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules.Repositories |
||||
|
{ |
||||
|
public interface IRuleEventRepository |
||||
|
{ |
||||
|
Task EnqueueAsync(RuleJob job, Instant nextAttempt); |
||||
|
|
||||
|
Task EnqueueAsync(Guid id, Instant nextAttempt); |
||||
|
|
||||
|
Task MarkSendingAsync(Guid jobId); |
||||
|
|
||||
|
Task TraceSentAsync(Guid jobId, string dump, RuleResult result, TimeSpan elapsed, Instant? nextCall); |
||||
|
|
||||
|
Task QueryPendingAsync(Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)); |
||||
|
|
||||
|
Task<int> CountByAppAsync(Guid appId); |
||||
|
|
||||
|
Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); |
||||
|
|
||||
|
Task<IRuleEventEntity> FindAsync(Guid id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IRuleRepository.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules.Repositories |
||||
|
{ |
||||
|
public interface IRuleRepository |
||||
|
{ |
||||
|
Task<IReadOnlyList<IRuleEntity>> QueryByAppAsync(Guid appId); |
||||
|
|
||||
|
Task<IReadOnlyList<IRuleEntity>> QueryCachedByAppAsync(Guid appId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDequeuer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using System.Threading.Tasks.Dataflow; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Read.Rules.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Timers; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem |
||||
|
{ |
||||
|
private readonly ActionBlock<IRuleEventEntity> requestBlock; |
||||
|
private readonly TransformBlock<IRuleEventEntity, IRuleEventEntity> blockBlock; |
||||
|
private readonly IRuleEventRepository ruleEventRepository; |
||||
|
private readonly RuleService ruleService; |
||||
|
private readonly CompletionTimer timer; |
||||
|
private readonly ISemanticLog log; |
||||
|
|
||||
|
public RuleDequeuer(RuleService ruleService, IRuleEventRepository ruleEventRepository, ISemanticLog log) |
||||
|
{ |
||||
|
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); |
||||
|
Guard.NotNull(ruleService, nameof(ruleService)); |
||||
|
Guard.NotNull(log, nameof(log)); |
||||
|
|
||||
|
this.ruleEventRepository = ruleEventRepository; |
||||
|
this.ruleService = ruleService; |
||||
|
|
||||
|
this.log = log; |
||||
|
|
||||
|
requestBlock = |
||||
|
new ActionBlock<IRuleEventEntity>(MakeRequestAsync, |
||||
|
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); |
||||
|
|
||||
|
blockBlock = |
||||
|
new TransformBlock<IRuleEventEntity, IRuleEventEntity>(x => BlockAsync(x), |
||||
|
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, BoundedCapacity = 1 }); |
||||
|
|
||||
|
blockBlock.LinkTo(requestBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||||
|
|
||||
|
timer = new CompletionTimer(5000, QueryAsync); |
||||
|
} |
||||
|
|
||||
|
protected override void DisposeObject(bool disposing) |
||||
|
{ |
||||
|
if (disposing) |
||||
|
{ |
||||
|
timer.StopAsync().Wait(); |
||||
|
|
||||
|
blockBlock.Complete(); |
||||
|
requestBlock.Completion.Wait(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Connect() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void Next() |
||||
|
{ |
||||
|
timer.SkipCurrentDelay(); |
||||
|
} |
||||
|
|
||||
|
private async Task QueryAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await ruleEventRepository.QueryPendingAsync(blockBlock.SendAsync, cancellationToken); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "QueueWebhookEvents") |
||||
|
.WriteProperty("status", "Failed")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task<IRuleEventEntity> BlockAsync(IRuleEventEntity @event) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await ruleEventRepository.MarkSendingAsync(@event.Id); |
||||
|
|
||||
|
return @event; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "BlockWebhookEvent") |
||||
|
.WriteProperty("status", "Failed")); |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async Task MakeRequestAsync(IRuleEventEntity @event) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var job = @event.Job; |
||||
|
|
||||
|
var response = await ruleService.InvokeAsync(job.ActionName, job.Details); |
||||
|
|
||||
|
Instant? nextCall = null; |
||||
|
|
||||
|
if (response.Result != RuleResult.Success) |
||||
|
{ |
||||
|
switch (@event.NumCalls) |
||||
|
{ |
||||
|
case 0: |
||||
|
nextCall = job.Created.Plus(Duration.FromMinutes(5)); |
||||
|
break; |
||||
|
case 1: |
||||
|
nextCall = job.Created.Plus(Duration.FromHours(1)); |
||||
|
break; |
||||
|
case 2: |
||||
|
nextCall = job.Created.Plus(Duration.FromHours(6)); |
||||
|
break; |
||||
|
case 3: |
||||
|
nextCall = job.Created.Plus(Duration.FromHours(12)); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
await ruleEventRepository.TraceSentAsync(@event.Id, response.Dump, response.Result, response.Elapsed, nextCall); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "SendWebhookEvent") |
||||
|
.WriteProperty("status", "Failed")); |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEnqueuer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Domain.Apps.Read.Rules.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public sealed class RuleEnqueuer : IEventConsumer |
||||
|
{ |
||||
|
private readonly IRuleEventRepository ruleEventRepository; |
||||
|
private readonly IRuleRepository ruleRepository; |
||||
|
private readonly RuleService ruleService; |
||||
|
|
||||
|
public string Name |
||||
|
{ |
||||
|
get { return GetType().Name; } |
||||
|
} |
||||
|
|
||||
|
public string EventsFilter |
||||
|
{ |
||||
|
get { return "^content-"; } |
||||
|
} |
||||
|
|
||||
|
public RuleEnqueuer( |
||||
|
IRuleEventRepository ruleEventRepository, |
||||
|
IRuleRepository ruleRepository, |
||||
|
RuleService ruleService) |
||||
|
{ |
||||
|
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); |
||||
|
Guard.NotNull(ruleRepository, nameof(ruleRepository)); |
||||
|
Guard.NotNull(ruleService, nameof(ruleService)); |
||||
|
|
||||
|
this.ruleEventRepository = ruleEventRepository; |
||||
|
this.ruleRepository = ruleRepository; |
||||
|
this.ruleService = ruleService; |
||||
|
} |
||||
|
|
||||
|
public Task ClearAsync() |
||||
|
{ |
||||
|
return TaskHelper.Done; |
||||
|
} |
||||
|
|
||||
|
public async Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
if (@event.Payload is AppEvent appEvent) |
||||
|
{ |
||||
|
var rules = await ruleRepository.QueryCachedByAppAsync(appEvent.AppId.Id); |
||||
|
|
||||
|
foreach (var ruleEntity in rules) |
||||
|
{ |
||||
|
var job = ruleService.CreateJob(ruleEntity.Rule, @event); |
||||
|
|
||||
|
if (job == null) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
await ruleEventRepository.EnqueueAsync(job, job.Created); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleJobResult.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public enum RuleJobResult |
||||
|
{ |
||||
|
Pending, |
||||
|
Success, |
||||
|
Retry, |
||||
|
Failed |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue