mirror of https://github.com/Squidex/squidex.git
274 changed files with 7489 additions and 4232 deletions
@ -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,46 @@ |
|||
// ==========================================================================
|
|||
// JsonRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Rules.Json |
|||
{ |
|||
public sealed class JsonRule |
|||
{ |
|||
[JsonProperty] |
|||
public RuleTrigger Trigger { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public RuleAction Action { get; set; } |
|||
|
|||
[JsonProperty] |
|||
public bool IsEnabled { get; set; } |
|||
|
|||
public JsonRule() |
|||
{ |
|||
} |
|||
|
|||
public JsonRule(Rule rule) |
|||
{ |
|||
SimpleMapper.Map(rule, this); |
|||
} |
|||
|
|||
public Rule ToRule() |
|||
{ |
|||
var rule = new Rule(Trigger, Action); |
|||
|
|||
if (!IsEnabled) |
|||
{ |
|||
rule.Disable(); |
|||
} |
|||
|
|||
return rule; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// RuleConverter.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Newtonsoft.Json; |
|||
using Squidex.Infrastructure.Json; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Rules.Json |
|||
{ |
|||
public sealed class RuleConverter : JsonClassConverter<Rule> |
|||
{ |
|||
protected override Rule ReadValue(JsonReader reader, JsonSerializer serializer) |
|||
{ |
|||
return serializer.Deserialize<JsonRule>(reader).ToRule(); |
|||
} |
|||
|
|||
protected override void WriteValue(JsonWriter writer, Rule value, JsonSerializer serializer) |
|||
{ |
|||
serializer.Serialize(writer, new JsonRule(value)); |
|||
} |
|||
} |
|||
} |
|||
@ -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() != action.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,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); |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,14 @@ |
|||
// ==========================================================================
|
|||
// UpdateWebhook.cs
|
|||
// SquidexCoreModel.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
|||
namespace Squidex.Domain.Apps.Core |
|||
{ |
|||
public sealed class UpdateWebhook : WebhookEditCommand |
|||
public static class SquidexCoreModel |
|||
{ |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -1,14 +1,14 @@ |
|||
// ==========================================================================
|
|||
// WebhookResult.cs
|
|||
// RuleResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks |
|||
namespace Squidex.Domain.Apps.Core.HandleRules |
|||
{ |
|||
public enum WebhookResult |
|||
public enum RuleResult |
|||
{ |
|||
Pending, |
|||
Success, |
|||
@ -0,0 +1,158 @@ |
|||
// ==========================================================================
|
|||
// 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 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 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 virtual 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 |
|||
{ |
|||
RuleId = 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 virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, RuleJobData 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("Elapsed {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 schema, SchemaEvent @event) |
|||
{ |
|||
return @event.SchemaId.Id == schema.SchemaId; |
|||
} |
|||
|
|||
private static bool MatchsType(ContentChangedTriggerSchema schema, SchemaEvent @event) |
|||
{ |
|||
return |
|||
(schema.SendCreate && @event is ContentCreated) || |
|||
(schema.SendUpdate && @event is ContentUpdated) || |
|||
(schema.SendDelete && @event is ContentDeleted) || |
|||
(schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); |
|||
} |
|||
} |
|||
} |
|||
@ -1,18 +1,21 @@ |
|||
// ==========================================================================
|
|||
// WebhookCreated.cs
|
|||
// 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.Webhooks |
|||
namespace Squidex.Domain.Apps.Events.Rules |
|||
{ |
|||
[EventType(nameof(WebhookCreated))] |
|||
public sealed class WebhookCreated : WebhookEditEvent |
|||
[EventType(nameof(RuleCreated))] |
|||
public sealed class RuleCreated : RuleEvent |
|||
{ |
|||
public string SharedSecret { get; set; } |
|||
public RuleTrigger Trigger { get; set; } |
|||
|
|||
public RuleAction Action { get; set; } |
|||
} |
|||
} |
|||
@ -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,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,43 @@ |
|||
// ==========================================================================
|
|||
// RuleEventDispatcher.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
|
|||
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 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(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,14 @@ |
|||
// ==========================================================================
|
|||
// DeleteWebhook.cs
|
|||
// SquidexEvents.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
|||
namespace Squidex.Domain.Apps.Events |
|||
{ |
|||
public sealed class DeleteWebhook : WebhookAggregateCommand |
|||
public static class SquidexEvents |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// ==========================================================================
|
|||
// MongoRuleEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
|||
{ |
|||
public class MongoRuleEntity : MongoEntity, IRuleEntity |
|||
{ |
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long Version { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
[BsonJson] |
|||
public Rule Rule { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
// ==========================================================================
|
|||
// MongoRuleRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Read.Rules.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
|||
{ |
|||
public partial class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, IEventConsumer |
|||
{ |
|||
private static readonly List<IRuleEntity> EmptyRules = new List<IRuleEntity>(); |
|||
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); |
|||
private Dictionary<Guid, List<IRuleEntity>> inMemoryRules; |
|||
|
|||
public MongoRuleRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "Projections_Rules"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection) |
|||
{ |
|||
return Task.WhenAll(collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId))); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IRuleEntity>> QueryByAppAsync(Guid appId) |
|||
{ |
|||
var entities = |
|||
await Collection.Find(x => x.AppId == appId) |
|||
.ToListAsync(); |
|||
|
|||
return entities.OfType<IRuleEntity>().ToList(); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IRuleEntity>> QueryCachedByAppAsync(Guid appId) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
return inMemoryRules.GetOrDefault(appId)?.ToList() ?? EmptyRules; |
|||
} |
|||
|
|||
private async Task EnsureRulesLoadedAsync() |
|||
{ |
|||
if (inMemoryRules == null) |
|||
{ |
|||
try |
|||
{ |
|||
await lockObject.WaitAsync(); |
|||
|
|||
if (inMemoryRules == null) |
|||
{ |
|||
inMemoryRules = new Dictionary<Guid, List<IRuleEntity>>(); |
|||
|
|||
var webhooks = |
|||
await Collection.Find(new BsonDocument()) |
|||
.ToListAsync(); |
|||
|
|||
foreach (var webhook in webhooks) |
|||
{ |
|||
inMemoryRules.GetOrAddNew(webhook.AppId).Add(webhook); |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
lockObject.Release(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
// ==========================================================================
|
|||
// MongoRuleRepository_EventHandling.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Events.Rules; |
|||
using Squidex.Domain.Apps.Events.Rules.Utils; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Rules |
|||
{ |
|||
public partial class MongoRuleRepository |
|||
{ |
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "^rule-"; } |
|||
} |
|||
|
|||
public Task On(Envelope<IEvent> @event) |
|||
{ |
|||
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
|||
} |
|||
|
|||
protected async Task On(RuleCreated @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
await Collection.CreateAsync(@event, headers, w => |
|||
{ |
|||
w.Rule = RuleEventDispatcher.Create(@event); |
|||
|
|||
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryRules.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(RuleUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
await Collection.UpdateAsync(@event, headers, w => |
|||
{ |
|||
w.Rule.Apply(@event); |
|||
|
|||
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryRules.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(RuleEnabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
await Collection.UpdateAsync(@event, headers, w => |
|||
{ |
|||
w.Rule.Apply(@event); |
|||
|
|||
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryRules.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(RuleDisabled @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
await Collection.UpdateAsync(@event, headers, w => |
|||
{ |
|||
w.Rule.Apply(@event); |
|||
|
|||
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryRules.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(RuleDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureRulesLoadedAsync(); |
|||
|
|||
inMemoryRules.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId); |
|||
|
|||
await Collection.DeleteManyAsync(x => x.Id == @event.RuleId); |
|||
} |
|||
} |
|||
} |
|||
@ -1,74 +0,0 @@ |
|||
// ==========================================================================
|
|||
// MongoWebhookEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Domain.Apps.Core.Webhooks; |
|||
using Squidex.Domain.Apps.Read.Webhooks; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|||
{ |
|||
public class MongoWebhookEntity : MongoEntity, IWebhookEntity |
|||
{ |
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Uri Url { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public Guid AppId { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long Version { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public RefToken CreatedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public RefToken LastModifiedBy { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string SharedSecret { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long TotalSucceeded { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long TotalFailed { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long TotalTimedout { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public long TotalRequestTime { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public List<WebhookSchema> Schemas { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public List<Guid> SchemaIds { get; set; } |
|||
|
|||
IEnumerable<WebhookSchema> IWebhookEntity.Schemas |
|||
{ |
|||
get { return Schemas; } |
|||
} |
|||
} |
|||
} |
|||
@ -1,119 +0,0 @@ |
|||
// ==========================================================================
|
|||
// MongoWebhookRepository.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Read.Webhooks; |
|||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|||
{ |
|||
public partial class MongoWebhookRepository : MongoRepositoryBase<MongoWebhookEntity>, IWebhookRepository, IEventConsumer |
|||
{ |
|||
private static readonly List<IWebhookEntity> EmptyWebhooks = new List<IWebhookEntity>(); |
|||
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); |
|||
private Dictionary<Guid, List<IWebhookEntity>> inMemoryWebhooks; |
|||
|
|||
public MongoWebhookRepository(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "Projections_SchemaWebhooks"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<MongoWebhookEntity> collection) |
|||
{ |
|||
return Task.WhenAll( |
|||
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)), |
|||
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaIds))); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IWebhookEntity>> QueryByAppAsync(Guid appId) |
|||
{ |
|||
var entities = |
|||
await Collection.Find(x => x.AppId == appId) |
|||
.ToListAsync(); |
|||
|
|||
return entities.OfType<IWebhookEntity>().ToList(); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IWebhookEntity>> QueryCachedByAppAsync(Guid appId) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyWebhooks; |
|||
} |
|||
|
|||
public async Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed) |
|||
{ |
|||
var webhookEntity = |
|||
await Collection.Find(x => x.Id == webhookId) |
|||
.FirstOrDefaultAsync(); |
|||
|
|||
if (webhookEntity != null) |
|||
{ |
|||
switch (result) |
|||
{ |
|||
case WebhookResult.Success: |
|||
webhookEntity.TotalSucceeded++; |
|||
break; |
|||
case WebhookResult.Failed: |
|||
webhookEntity.TotalFailed++; |
|||
break; |
|||
case WebhookResult.Timeout: |
|||
webhookEntity.TotalTimedout++; |
|||
break; |
|||
} |
|||
|
|||
webhookEntity.TotalRequestTime += (long)elapsed.TotalMilliseconds; |
|||
|
|||
await Collection.ReplaceOneAsync(x => x.Id == webhookId, webhookEntity); |
|||
} |
|||
} |
|||
|
|||
private async Task EnsureWebooksLoadedAsync() |
|||
{ |
|||
if (inMemoryWebhooks == null) |
|||
{ |
|||
try |
|||
{ |
|||
await lockObject.WaitAsync(); |
|||
|
|||
if (inMemoryWebhooks == null) |
|||
{ |
|||
inMemoryWebhooks = new Dictionary<Guid, List<IWebhookEntity>>(); |
|||
|
|||
var webhooks = |
|||
await Collection.Find(new BsonDocument()) |
|||
.ToListAsync(); |
|||
|
|||
foreach (var webhook in webhooks) |
|||
{ |
|||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
lockObject.Release(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,99 +0,0 @@ |
|||
// ==========================================================================
|
|||
// MongoSchemaWebhookRepository_EventHandling.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Events.Schemas; |
|||
using Squidex.Domain.Apps.Events.Webhooks; |
|||
using Squidex.Domain.Apps.Read.MongoDb.Utils; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks |
|||
{ |
|||
public partial class MongoWebhookRepository |
|||
{ |
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "(^webhook-)|(^schema-)"; } |
|||
} |
|||
|
|||
public Task On(Envelope<IEvent> @event) |
|||
{ |
|||
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
|||
} |
|||
|
|||
protected async Task On(WebhookCreated @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
await Collection.CreateAsync(@event, headers, w => |
|||
{ |
|||
SimpleMapper.Map(@event, w); |
|||
|
|||
w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); |
|||
|
|||
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(WebhookUpdated @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
await Collection.UpdateAsync(@event, headers, w => |
|||
{ |
|||
SimpleMapper.Map(@event, w); |
|||
|
|||
w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); |
|||
|
|||
inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); |
|||
inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
var webhooks = |
|||
await Collection.Find(t => t.SchemaIds.Contains(@event.SchemaId.Id)) |
|||
.ToListAsync(); |
|||
|
|||
foreach (var webhook in webhooks) |
|||
{ |
|||
webhook.Schemas.RemoveAll(s => s.SchemaId == @event.SchemaId.Id); |
|||
|
|||
webhook.SchemaIds = webhook.Schemas.Select(x => x.SchemaId).ToList(); |
|||
|
|||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).RemoveAll(x => x.Id == webhook.Id); |
|||
inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); |
|||
|
|||
await Collection.ReplaceOneAsync(x => x.Id == webhook.Id, webhook); |
|||
} |
|||
} |
|||
|
|||
protected async Task On(WebhookDeleted @event, EnvelopeHeaders headers) |
|||
{ |
|||
await EnsureWebooksLoadedAsync(); |
|||
|
|||
inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.WebhookId); |
|||
|
|||
await Collection.DeleteManyAsync(x => x.Id == @event.WebhookId); |
|||
} |
|||
} |
|||
} |
|||
@ -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,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 MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall); |
|||
|
|||
Task QueryPendingAsync(Instant now, 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,72 @@ |
|||
// ==========================================================================
|
|||
// 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 ".*"; } |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
await ruleEventRepository.EnqueueAsync(job, job.Created); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,14 +1,14 @@ |
|||
// ==========================================================================
|
|||
// WebhookJobResult.cs
|
|||
// RuleJobResult.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks |
|||
namespace Squidex.Domain.Apps.Read.Rules |
|||
{ |
|||
public enum WebhookJobResult |
|||
public enum RuleJobResult |
|||
{ |
|||
Pending, |
|||
Success, |
|||
@ -1,31 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IWebhookEntity.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Domain.Apps.Core.Webhooks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks |
|||
{ |
|||
public interface IWebhookEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion |
|||
{ |
|||
Uri Url { get; } |
|||
|
|||
long TotalSucceeded { get; } |
|||
|
|||
long TotalFailed { get; } |
|||
|
|||
long TotalTimedout { get; } |
|||
|
|||
long TotalRequestTime { get; } |
|||
|
|||
string SharedSecret { get; } |
|||
|
|||
IEnumerable<WebhookSchema> Schemas { get; } |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// ==========================================================================
|
|||
// IWebhookEventRepository.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; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks.Repositories |
|||
{ |
|||
public interface IWebhookEventRepository |
|||
{ |
|||
Task EnqueueAsync(WebhookJob job, Instant nextAttempt); |
|||
|
|||
Task EnqueueAsync(Guid id, Instant nextAttempt); |
|||
|
|||
Task TraceSendingAsync(Guid jobId); |
|||
|
|||
Task TraceSentAsync(Guid jobId, string dump, WebhookResult result, TimeSpan elapsed, Instant? nextCall); |
|||
|
|||
Task QueryPendingAsync(Func<IWebhookEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)); |
|||
|
|||
Task<int> CountByAppAsync(Guid appId); |
|||
|
|||
Task<IReadOnlyList<IWebhookEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); |
|||
|
|||
Task<IWebhookEventEntity> FindAsync(Guid id); |
|||
} |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
// ==========================================================================
|
|||
// ISchemaWebhookRepository.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.Webhooks.Repositories |
|||
{ |
|||
public interface IWebhookRepository |
|||
{ |
|||
Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed); |
|||
|
|||
Task<IReadOnlyList<IWebhookEntity>> QueryByAppAsync(Guid appId); |
|||
|
|||
Task<IReadOnlyList<IWebhookEntity>> QueryCachedByAppAsync(Guid appId); |
|||
} |
|||
} |
|||
@ -1,140 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebhookEnqueuer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Webhooks; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Contents; |
|||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks |
|||
{ |
|||
public sealed class WebhookEnqueuer : IEventConsumer |
|||
{ |
|||
private const string ContentPrefix = "Content"; |
|||
private static readonly Duration ExpirationTime = Duration.FromDays(2); |
|||
private readonly IWebhookEventRepository webhookEventRepository; |
|||
private readonly IWebhookRepository webhookRepository; |
|||
private readonly IClock clock; |
|||
private readonly TypeNameRegistry typeNameRegistry; |
|||
private readonly JsonSerializer webhookSerializer; |
|||
|
|||
public string Name |
|||
{ |
|||
get { return GetType().Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return "^content-"; } |
|||
} |
|||
|
|||
public WebhookEnqueuer(TypeNameRegistry typeNameRegistry, |
|||
IWebhookEventRepository webhookEventRepository, |
|||
IWebhookRepository webhookRepository, |
|||
IClock clock, |
|||
JsonSerializer webhookSerializer) |
|||
{ |
|||
Guard.NotNull(webhookEventRepository, nameof(webhookEventRepository)); |
|||
Guard.NotNull(webhookSerializer, nameof(webhookSerializer)); |
|||
Guard.NotNull(webhookRepository, nameof(webhookRepository)); |
|||
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); |
|||
Guard.NotNull(clock, nameof(clock)); |
|||
|
|||
this.webhookEventRepository = webhookEventRepository; |
|||
this.webhookSerializer = webhookSerializer; |
|||
this.webhookRepository = webhookRepository; |
|||
|
|||
this.clock = clock; |
|||
|
|||
this.typeNameRegistry = typeNameRegistry; |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public async Task On(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Payload is ContentEvent contentEvent) |
|||
{ |
|||
var eventType = typeNameRegistry.GetName(@event.Payload.GetType()); |
|||
|
|||
var webhooks = await webhookRepository.QueryCachedByAppAsync(contentEvent.AppId.Id); |
|||
|
|||
var matchingWebhooks = webhooks.Where(w => w.Schemas.Any(s => Matchs(s, contentEvent))).ToList(); |
|||
|
|||
if (matchingWebhooks.Count > 0) |
|||
{ |
|||
var now = clock.GetCurrentInstant(); |
|||
|
|||
var eventPayload = CreatePayload(@event, eventType); |
|||
var eventName = $"{contentEvent.SchemaId.Name.ToPascalCase()}{CreateContentEventName(eventType)}"; |
|||
|
|||
foreach (var webhook in matchingWebhooks) |
|||
{ |
|||
await EnqueueJobAsync(eventPayload, webhook, contentEvent, eventName, now); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private async Task EnqueueJobAsync(string payload, IWebhookEntity webhook, AppEvent contentEvent, string eventName, Instant now) |
|||
{ |
|||
var signature = $"{payload}{webhook.SharedSecret}".Sha256Base64(); |
|||
|
|||
var job = new WebhookJob |
|||
{ |
|||
Id = Guid.NewGuid(), |
|||
AppId = contentEvent.AppId.Id, |
|||
RequestUrl = webhook.Url, |
|||
RequestBody = payload, |
|||
RequestSignature = signature, |
|||
EventName = eventName, |
|||
Expires = now.Plus(ExpirationTime), |
|||
WebhookId = webhook.Id |
|||
}; |
|||
|
|||
await webhookEventRepository.EnqueueAsync(job, now); |
|||
} |
|||
|
|||
private static bool Matchs(WebhookSchema webhookSchema, SchemaEvent @event) |
|||
{ |
|||
return |
|||
(@event.SchemaId.Id == webhookSchema.SchemaId) && |
|||
(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); |
|||
} |
|||
|
|||
private string CreatePayload(Envelope<IEvent> @event, string eventType) |
|||
{ |
|||
return new JObject( |
|||
new JProperty("type", eventType), |
|||
new JProperty("payload", JObject.FromObject(@event.Payload, webhookSerializer)), |
|||
new JProperty("timestamp", @event.Headers.Timestamp().ToString())) |
|||
.ToString(Formatting.Indented); |
|||
} |
|||
|
|||
private static string CreateContentEventName(string eventType) |
|||
{ |
|||
return eventType.StartsWith(ContentPrefix, StringComparison.Ordinal) ? eventType.Substring(ContentPrefix.Length) : eventType; |
|||
} |
|||
} |
|||
} |
|||
@ -1,98 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebhookSender.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Net.Http; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.Http; |
|||
|
|||
namespace Squidex.Domain.Apps.Read.Webhooks |
|||
{ |
|||
public class WebhookSender |
|||
{ |
|||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
|||
|
|||
public virtual async Task<(string Dump, WebhookResult Result, TimeSpan Elapsed)> SendAsync(WebhookJob job) |
|||
{ |
|||
try |
|||
{ |
|||
var request = BuildRequest(job); |
|||
HttpResponseMessage response = null; |
|||
|
|||
var responseString = string.Empty; |
|||
|
|||
var isTimeout = false; |
|||
|
|||
var watch = Stopwatch.StartNew(); |
|||
|
|||
try |
|||
{ |
|||
using (var client = new HttpClient { Timeout = Timeout }) |
|||
{ |
|||
response = await client.SendAsync(request); |
|||
} |
|||
} |
|||
catch (TimeoutException) |
|||
{ |
|||
isTimeout = true; |
|||
} |
|||
catch (OperationCanceledException) |
|||
{ |
|||
isTimeout = true; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
responseString = ex.Message; |
|||
} |
|||
finally |
|||
{ |
|||
watch.Stop(); |
|||
} |
|||
|
|||
if (response != null) |
|||
{ |
|||
responseString = await response.Content.ReadAsStringAsync(); |
|||
} |
|||
|
|||
var dump = DumpFormatter.BuildDump(request, response, job.RequestBody, responseString, watch.Elapsed, isTimeout); |
|||
|
|||
var result = WebhookResult.Failed; |
|||
|
|||
if (isTimeout) |
|||
{ |
|||
result = WebhookResult.Timeout; |
|||
} |
|||
else if (response?.IsSuccessStatusCode == true) |
|||
{ |
|||
result = WebhookResult.Success; |
|||
} |
|||
|
|||
return (dump, result, watch.Elapsed); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return (ex.Message, WebhookResult.Failed, TimeSpan.Zero); |
|||
} |
|||
} |
|||
|
|||
private static HttpRequestMessage BuildRequest(WebhookJob job) |
|||
{ |
|||
var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) |
|||
{ |
|||
Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") |
|||
}; |
|||
|
|||
request.Headers.Add("X-Signature", job.RequestSignature); |
|||
request.Headers.Add("User-Agent", "Squidex Webhook"); |
|||
|
|||
return request; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
// ==========================================================================
|
|||
// CreateRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public sealed class CreateRule : RuleEditCommand |
|||
{ |
|||
public CreateRule() |
|||
{ |
|||
RuleId = Guid.NewGuid(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// DeleteRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public sealed class DeleteRule : RuleAggregateCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// DisableRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public sealed class DisableRule : RuleAggregateCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// EnableRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public sealed class EnableRule : RuleAggregateCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// RuleEditCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public abstract class RuleEditCommand : RuleAggregateCommand |
|||
{ |
|||
public RuleTrigger Trigger { get; set; } |
|||
|
|||
public RuleAction Action { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// UpdateRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Commands |
|||
{ |
|||
public sealed class UpdateRule : RuleEditCommand |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,107 @@ |
|||
// ==========================================================================
|
|||
// GuardRule.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Write.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Guards |
|||
{ |
|||
public static class GuardRule |
|||
{ |
|||
public static Task CanCreate(CreateRule command, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
return Validate.It(() => "Cannot create rule.", async error => |
|||
{ |
|||
if (command.Trigger == null) |
|||
{ |
|||
error(new ValidationError("Trigger must be defined.", nameof(command.Trigger))); |
|||
} |
|||
else |
|||
{ |
|||
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); |
|||
|
|||
errors.Foreach(error); |
|||
} |
|||
|
|||
if (command.Action == null) |
|||
{ |
|||
error(new ValidationError("Trigger must be defined.", nameof(command.Action))); |
|||
} |
|||
else |
|||
{ |
|||
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
|||
|
|||
errors.Foreach(error); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static Task CanUpdate(UpdateRule command, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
return Validate.It(() => "Cannot update rule.", async error => |
|||
{ |
|||
if (command.Trigger == null && command.Action == null) |
|||
{ |
|||
error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action))); |
|||
} |
|||
|
|||
if (command.Trigger != null) |
|||
{ |
|||
var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); |
|||
|
|||
errors.Foreach(error); |
|||
} |
|||
|
|||
if (command.Action != null) |
|||
{ |
|||
var errors = await RuleActionValidator.ValidateAsync(command.Action); |
|||
|
|||
errors.Foreach(error); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanEnable(EnableRule command, Rule rule) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
Validate.It(() => "Cannot enable rule.", error => |
|||
{ |
|||
if (rule.IsEnabled) |
|||
{ |
|||
error(new ValidationError("Rule is already enabled.")); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanDisable(DisableRule command, Rule rule) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
Validate.It(() => "Cannot disable rule.", error => |
|||
{ |
|||
if (!rule.IsEnabled) |
|||
{ |
|||
error(new ValidationError("Rule is already disabled.")); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public static void CanDelete(DeleteRule command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// ==========================================================================
|
|||
// RuleActionValidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Actions; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Guards |
|||
{ |
|||
public sealed class RuleActionValidator : IRuleActionVisitor<Task<IEnumerable<ValidationError>>> |
|||
{ |
|||
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleAction action) |
|||
{ |
|||
Guard.NotNull(action, nameof(action)); |
|||
|
|||
var visitor = new RuleActionValidator(); |
|||
|
|||
return action.Accept(visitor); |
|||
} |
|||
|
|||
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action) |
|||
{ |
|||
var errors = new List<ValidationError>(); |
|||
|
|||
if (action.Url == null || !action.Url.IsAbsoluteUri) |
|||
{ |
|||
errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url))); |
|||
} |
|||
|
|||
return Task.FromResult<IEnumerable<ValidationError>>(errors); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// ==========================================================================
|
|||
// RuleTriggerValidator.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Triggers; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules.Guards |
|||
{ |
|||
public sealed class RuleTriggerValidator : IRuleTriggerVisitor<Task<IEnumerable<ValidationError>>> |
|||
{ |
|||
public ISchemaProvider Schemas { get; } |
|||
|
|||
public RuleTriggerValidator(ISchemaProvider schemas) |
|||
{ |
|||
Schemas = schemas; |
|||
} |
|||
|
|||
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleTrigger action, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(action, nameof(action)); |
|||
Guard.NotNull(schemas, nameof(schemas)); |
|||
|
|||
var visitor = new RuleTriggerValidator(schemas); |
|||
|
|||
return action.Accept(visitor); |
|||
} |
|||
|
|||
public async Task<IEnumerable<ValidationError>> Visit(ContentChangedTrigger trigger) |
|||
{ |
|||
if (trigger.Schemas != null) |
|||
{ |
|||
var schemaErrors = await Task.WhenAll( |
|||
trigger.Schemas.Select(async s => |
|||
await Schemas.FindSchemaByIdAsync(s.SchemaId) == null |
|||
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas)) |
|||
: null)); |
|||
|
|||
return schemaErrors.Where(x => x != null).ToList(); |
|||
} |
|||
|
|||
return new List<ValidationError>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
// ==========================================================================
|
|||
// RuleCommandMiddleware.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Write.Rules.Commands; |
|||
using Squidex.Domain.Apps.Write.Rules.Guards; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules |
|||
{ |
|||
public class RuleCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IAggregateHandler handler; |
|||
private readonly ISchemaProvider schemas; |
|||
|
|||
public RuleCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(handler, nameof(handler)); |
|||
Guard.NotNull(schemas, nameof(schemas)); |
|||
|
|||
this.handler = handler; |
|||
this.schemas = schemas; |
|||
} |
|||
|
|||
protected Task On(CreateRule command, CommandContext context) |
|||
{ |
|||
return handler.CreateAsync<RuleDomainObject>(context, async w => |
|||
{ |
|||
await GuardRule.CanCreate(command, schemas); |
|||
|
|||
w.Create(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(UpdateRule command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<RuleDomainObject>(context, async c => |
|||
{ |
|||
await GuardRule.CanUpdate(command, schemas); |
|||
|
|||
c.Update(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(EnableRule command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<RuleDomainObject>(context, r => |
|||
{ |
|||
GuardRule.CanEnable(command, r.Rule); |
|||
|
|||
r.Enable(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(DisableRule command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<RuleDomainObject>(context, r => |
|||
{ |
|||
GuardRule.CanDisable(command, r.Rule); |
|||
|
|||
r.Disable(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(DeleteRule command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<RuleDomainObject>(context, c => |
|||
{ |
|||
GuardRule.CanDelete(command); |
|||
|
|||
c.Delete(command); |
|||
}); |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (!await this.DispatchActionAsync(context.Command, context)) |
|||
{ |
|||
await next(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,118 @@ |
|||
// ==========================================================================
|
|||
// RuleDomainObject.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Events.Rules; |
|||
using Squidex.Domain.Apps.Events.Rules.Utils; |
|||
using Squidex.Domain.Apps.Write.Rules.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Rules |
|||
{ |
|||
public class RuleDomainObject : DomainObjectBase |
|||
{ |
|||
private Rule rule; |
|||
private bool isDeleted; |
|||
|
|||
public Rule Rule |
|||
{ |
|||
get { return rule; } |
|||
} |
|||
|
|||
public RuleDomainObject(Guid id, int version) |
|||
: base(id, version) |
|||
{ |
|||
} |
|||
|
|||
protected void On(RuleCreated @event) |
|||
{ |
|||
rule = RuleEventDispatcher.Create(@event); |
|||
} |
|||
|
|||
protected void On(RuleUpdated @event) |
|||
{ |
|||
rule.Apply(@event); |
|||
} |
|||
|
|||
protected void On(RuleEnabled @event) |
|||
{ |
|||
rule.Apply(@event); |
|||
} |
|||
|
|||
protected void On(RuleDisabled @event) |
|||
{ |
|||
rule.Apply(@event); |
|||
} |
|||
|
|||
protected void On(RuleDeleted @event) |
|||
{ |
|||
isDeleted = true; |
|||
} |
|||
|
|||
public void Create(CreateRule command) |
|||
{ |
|||
VerifyNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); |
|||
} |
|||
|
|||
public void Update(UpdateRule command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); |
|||
} |
|||
|
|||
public void Enable(EnableRule command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); |
|||
} |
|||
|
|||
public void Disable(DisableRule command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); |
|||
} |
|||
|
|||
public void Delete(DeleteRule command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); |
|||
} |
|||
|
|||
private void VerifyNotCreated() |
|||
{ |
|||
if (rule != null) |
|||
{ |
|||
throw new DomainException("Webhook has already been created."); |
|||
} |
|||
} |
|||
|
|||
private void VerifyCreatedAndNotDeleted() |
|||
{ |
|||
if (isDeleted || rule == null) |
|||
{ |
|||
throw new DomainException("Webhook has already been deleted or not created yet."); |
|||
} |
|||
} |
|||
|
|||
protected override void DispatchEvent(Envelope<IEvent> @event) |
|||
{ |
|||
this.DispatchAction(@event.Payload); |
|||
} |
|||
} |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebhookEditCommand.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Domain.Apps.Core.Webhooks; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks.Commands |
|||
{ |
|||
public abstract class WebhookEditCommand : WebhookAggregateCommand |
|||
{ |
|||
public Uri Url { get; set; } |
|||
|
|||
public List<WebhookSchema> Schemas { get; set; } = new List<WebhookSchema>(); |
|||
} |
|||
} |
|||
@ -1,61 +0,0 @@ |
|||
// ==========================================================================
|
|||
// GuardWebhook.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks.Guards |
|||
{ |
|||
public static class GuardWebhook |
|||
{ |
|||
public static Task CanCreate(CreateWebhook command, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
return Validate.It(() => "Cannot create webhook.", error => ValidateCommandAsync(command, error, schemas)); |
|||
} |
|||
|
|||
public static Task CanUpdate(UpdateWebhook command, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
|
|||
return Validate.It(() => "Cannot update webhook.", error => ValidateCommandAsync(command, error, schemas)); |
|||
} |
|||
|
|||
public static void CanDelete(DeleteWebhook command) |
|||
{ |
|||
Guard.NotNull(command, nameof(command)); |
|||
} |
|||
|
|||
private static async Task ValidateCommandAsync(WebhookEditCommand command, Action<ValidationError> error, ISchemaProvider schemas) |
|||
{ |
|||
if (command.Url == null || !command.Url.IsAbsoluteUri) |
|||
{ |
|||
error(new ValidationError("Url must be specified and absolute.", nameof(command.Url))); |
|||
} |
|||
|
|||
if (command.Schemas != null) |
|||
{ |
|||
var schemaErrors = await Task.WhenAll( |
|||
command.Schemas.Select(async s => |
|||
await schemas.FindSchemaByIdAsync(s.SchemaId) == null |
|||
? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(command.Schemas)) |
|||
: null)); |
|||
|
|||
foreach (var schemaError in schemaErrors.Where(x => x != null)) |
|||
{ |
|||
error(schemaError); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebhookCommandMiddleware.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Read.Schemas.Services; |
|||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|||
using Squidex.Domain.Apps.Write.Webhooks.Guards; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Commands; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks |
|||
{ |
|||
public class WebhookCommandMiddleware : ICommandMiddleware |
|||
{ |
|||
private readonly IAggregateHandler handler; |
|||
private readonly ISchemaProvider schemas; |
|||
|
|||
public WebhookCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) |
|||
{ |
|||
Guard.NotNull(handler, nameof(handler)); |
|||
Guard.NotNull(schemas, nameof(schemas)); |
|||
|
|||
this.handler = handler; |
|||
this.schemas = schemas; |
|||
} |
|||
|
|||
protected async Task On(CreateWebhook command, CommandContext context) |
|||
{ |
|||
await handler.CreateAsync<WebhookDomainObject>(context, async w => |
|||
{ |
|||
await GuardWebhook.CanCreate(command, schemas); |
|||
|
|||
w.Create(command); |
|||
}); |
|||
} |
|||
|
|||
protected async Task On(UpdateWebhook command, CommandContext context) |
|||
{ |
|||
await handler.UpdateAsync<WebhookDomainObject>(context, async c => |
|||
{ |
|||
await GuardWebhook.CanUpdate(command, schemas); |
|||
|
|||
c.Update(command); |
|||
}); |
|||
} |
|||
|
|||
protected Task On(DeleteWebhook command, CommandContext context) |
|||
{ |
|||
return handler.UpdateAsync<WebhookDomainObject>(context, c => |
|||
{ |
|||
GuardWebhook.CanDelete(command); |
|||
|
|||
c.Delete(command); |
|||
}); |
|||
} |
|||
|
|||
public async Task HandleAsync(CommandContext context, Func<Task> next) |
|||
{ |
|||
if (!await this.DispatchActionAsync(context.Command, context)) |
|||
{ |
|||
await next(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,82 +0,0 @@ |
|||
// ==========================================================================
|
|||
// WebhookDomainObject.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Events.Webhooks; |
|||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Dispatching; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Domain.Apps.Write.Webhooks |
|||
{ |
|||
public class WebhookDomainObject : DomainObjectBase |
|||
{ |
|||
private bool isDeleted; |
|||
private bool isCreated; |
|||
|
|||
public WebhookDomainObject(Guid id, int version) |
|||
: base(id, version) |
|||
{ |
|||
} |
|||
|
|||
protected void On(WebhookCreated @event) |
|||
{ |
|||
isCreated = true; |
|||
} |
|||
|
|||
protected void On(WebhookDeleted @event) |
|||
{ |
|||
isDeleted = true; |
|||
} |
|||
|
|||
public void Create(CreateWebhook command) |
|||
{ |
|||
VerifyNotCreated(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new WebhookCreated())); |
|||
} |
|||
|
|||
public void Update(UpdateWebhook command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new WebhookUpdated())); |
|||
} |
|||
|
|||
public void Delete(DeleteWebhook command) |
|||
{ |
|||
VerifyCreatedAndNotDeleted(); |
|||
|
|||
RaiseEvent(SimpleMapper.Map(command, new WebhookDeleted())); |
|||
} |
|||
|
|||
private void VerifyNotCreated() |
|||
{ |
|||
if (isCreated) |
|||
{ |
|||
throw new DomainException("Webhook has already been created."); |
|||
} |
|||
} |
|||
|
|||
private void VerifyCreatedAndNotDeleted() |
|||
{ |
|||
if (isDeleted || !isCreated) |
|||
{ |
|||
throw new DomainException("Webhook has already been deleted or not created yet."); |
|||
} |
|||
} |
|||
|
|||
protected override void DispatchEvent(Envelope<IEvent> @event) |
|||
{ |
|||
this.DispatchAction(@event.Payload); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// SquidexInfrastructure.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure |
|||
{ |
|||
public static class SquidexInfrastructure |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// ==========================================================================
|
|||
// AppCreatedDto.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Converters; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
|
|||
namespace Squidex.Controllers.Api.Apps.Models |
|||
{ |
|||
public sealed class AppCreatedDto |
|||
{ |
|||
/// <summary>
|
|||
/// Id of the created entity.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string Id { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The new version of the entity.
|
|||
/// </summary>
|
|||
public long Version { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The permission level of the user.
|
|||
/// </summary>
|
|||
[JsonConverter(typeof(StringEnumConverter))] |
|||
public AppContributorPermission Permission { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the current plan name.
|
|||
/// </summary>
|
|||
public string PlanName { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the next plan name.
|
|||
/// </summary>
|
|||
public string PlanUpgrade { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// ==========================================================================
|
|||
// RuleActionDtoFactory.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Controllers.Api.Rules.Models.Actions; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Actions; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Controllers.Api.Rules.Models.Converters |
|||
{ |
|||
public sealed class RuleActionDtoFactory : IRuleActionVisitor<RuleActionDto> |
|||
{ |
|||
private static readonly RuleActionDtoFactory Instance = new RuleActionDtoFactory(); |
|||
|
|||
private RuleActionDtoFactory() |
|||
{ |
|||
} |
|||
|
|||
public static RuleActionDto Create(RuleAction properties) |
|||
{ |
|||
return properties.Accept(Instance); |
|||
} |
|||
|
|||
public RuleActionDto Visit(WebhookAction action) |
|||
{ |
|||
return SimpleMapper.Map(action, new WebhookActionDto()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
// ==========================================================================
|
|||
// RuleConverter.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Domain.Apps.Read.Rules; |
|||
using Squidex.Domain.Apps.Write.Rules.Commands; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Controllers.Api.Rules.Models.Converters |
|||
{ |
|||
public static class RuleConverter |
|||
{ |
|||
public static RuleDto ToModel(this IRuleEntity entity) |
|||
{ |
|||
var dto = new RuleDto(); |
|||
|
|||
SimpleMapper.Map(entity, dto); |
|||
SimpleMapper.Map(entity.Rule, dto); |
|||
|
|||
if (entity.Rule.Trigger != null) |
|||
{ |
|||
dto.Trigger = RuleTriggerDtoFactory.Create(entity.Rule.Trigger); |
|||
} |
|||
|
|||
if (entity.Rule.Action != null) |
|||
{ |
|||
dto.Action = RuleActionDtoFactory.Create(entity.Rule.Action); |
|||
} |
|||
|
|||
return dto; |
|||
} |
|||
|
|||
public static UpdateRule ToCommand(this UpdateRuleDto dto, Guid id) |
|||
{ |
|||
var command = new UpdateRule { RuleId = id }; |
|||
|
|||
if (dto.Action != null) |
|||
{ |
|||
command.Action = dto.Action.ToAction(); |
|||
} |
|||
|
|||
if (dto.Trigger != null) |
|||
{ |
|||
command.Trigger = dto.Trigger.ToTrigger(); |
|||
} |
|||
|
|||
return command; |
|||
} |
|||
|
|||
public static CreateRule ToCommand(this CreateRuleDto dto) |
|||
{ |
|||
var command = new CreateRule(); |
|||
|
|||
if (dto.Action != null) |
|||
{ |
|||
command.Action = dto.Action.ToAction(); |
|||
} |
|||
|
|||
if (dto.Trigger != null) |
|||
{ |
|||
command.Trigger = dto.Trigger.ToTrigger(); |
|||
} |
|||
|
|||
return command; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// RuleTriggerDtoFactory.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Linq; |
|||
using Squidex.Controllers.Api.Rules.Models.Triggers; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Triggers; |
|||
using Squidex.Infrastructure.Reflection; |
|||
|
|||
namespace Squidex.Controllers.Api.Rules.Models.Converters |
|||
{ |
|||
public sealed class RuleTriggerDtoFactory : IRuleTriggerVisitor<RuleTriggerDto> |
|||
{ |
|||
private static readonly RuleTriggerDtoFactory Instance = new RuleTriggerDtoFactory(); |
|||
|
|||
private RuleTriggerDtoFactory() |
|||
{ |
|||
} |
|||
|
|||
public static RuleTriggerDto Create(RuleTrigger properties) |
|||
{ |
|||
return properties.Accept(Instance); |
|||
} |
|||
|
|||
public RuleTriggerDto Visit(ContentChangedTrigger trigger) |
|||
{ |
|||
return new ContentChangedTriggerDto |
|||
{ |
|||
Schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToList() |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue