diff --git a/NuGet.Config b/NuGet.Config index 3f0e00340..b49f363c2 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,6 +1,7 @@  + \ No newline at end of file diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs index a663a7f74..5c6dd5b87 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs @@ -30,9 +30,11 @@ namespace Squidex.Extensions.Actions.Fastly protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action) { + var id = @event is EnrichedEntityEvent entityEvent ? entityEvent.Id.ToString() : string.Empty; + var ruleJob = new FastlyJob { - Key = @event.AggregateId.ToString(), + Key = id, FastlyApiKey = action.ApiKey, FastlyServiceID = action.ServiceId }; diff --git a/extensions/Squidex.Extensions/Squidex.Extensions.csproj b/extensions/Squidex.Extensions/Squidex.Extensions.csproj index 50361886d..8ec17c724 100644 --- a/extensions/Squidex.Extensions/Squidex.Extensions.csproj +++ b/extensions/Squidex.Extensions/Squidex.Extensions.csproj @@ -10,13 +10,13 @@ - + - + - + - + diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs index 0793615af..3edd96d94 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs @@ -11,8 +11,8 @@ namespace Squidex.Domain.Apps.Core.Rules { public interface IRuleTriggerVisitor { - T Visit(AssetChangedTrigger trigger); + T Visit(AssetChangedTriggerV2 trigger); - T Visit(ContentChangedTrigger trigger); + T Visit(ContentChangedTriggerV2 trigger); } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/JsonRule.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/JsonRule.cs index 8cf13f2a3..146a3243b 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/JsonRule.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/JsonRule.cs @@ -6,6 +6,7 @@ // ========================================================================== using Newtonsoft.Json; +using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Core.Rules.Json @@ -32,7 +33,14 @@ namespace Squidex.Domain.Apps.Core.Rules.Json public Rule ToRule() { - var rule = new Rule(Trigger, Action); + var trigger = Trigger; + + if (trigger is IMigrated migrated) + { + trigger = migrated.Migrate(); + } + + var rule = new Rule(trigger, Action); if (!IsEnabled) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs index 811cd51f5..cbda50c4e 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs @@ -16,8 +16,6 @@ namespace Squidex.Domain.Apps.Core.Rules public Guid AppId { get; set; } - public Guid AggregateId { get; set; } - public string EventName { get; set; } public string ActionName { get; set; } @@ -26,6 +24,8 @@ namespace Squidex.Domain.Apps.Core.Rules public string Description { get; set; } + public long ExecutionPartition { get; set; } + public Instant Created { get; set; } public Instant Expires { get; set; } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTrigger.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTriggerV2.cs similarity index 67% rename from src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTrigger.cs rename to src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTriggerV2.cs index 67f099873..70c9bc3ad 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTrigger.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/AssetChangedTriggerV2.cs @@ -9,16 +9,10 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Rules.Triggers { - [TypeName(nameof(AssetChangedTrigger))] - public sealed class AssetChangedTrigger : RuleTrigger + [TypeName(nameof(AssetChangedTriggerV2))] + public sealed class AssetChangedTriggerV2 : RuleTrigger { - public bool SendCreate { get; set; } - - public bool SendUpdate { get; set; } - - public bool SendRename { get; set; } - - public bool SendDelete { get; set; } + public string Condition { get; set; } public override T Accept(IRuleTriggerVisitor visitor) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchema.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchemaV2.cs similarity index 55% rename from src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchema.cs rename to src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchemaV2.cs index cfcb516d1..76086d174 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchema.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchemaV2.cs @@ -9,22 +9,10 @@ using System; namespace Squidex.Domain.Apps.Core.Rules.Triggers { - public sealed class ContentChangedTriggerSchema : Freezable + public sealed class ContentChangedTriggerSchemaV2 : Freezable { 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; } - - public bool SendUnpublish { get; set; } - - public bool SendArchived { get; set; } - - public bool SendRestore { get; set; } + public string Condition { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerV2.cs similarity index 82% rename from src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs rename to src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerV2.cs index a4db365a5..ed1d9b033 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerV2.cs @@ -10,10 +10,10 @@ using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Rules.Triggers { - [TypeName(nameof(ContentChangedTrigger))] - public sealed class ContentChangedTrigger : RuleTrigger + [TypeName(nameof(ContentChangedTriggerV2))] + public sealed class ContentChangedTriggerV2 : RuleTrigger { - public ReadOnlyCollection Schemas { get; set; } + public ReadOnlyCollection Schemas { get; set; } public bool HandleAll { get; set; } diff --git a/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj b/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj index 366672e5b..5c0960c6c 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj +++ b/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj @@ -8,7 +8,7 @@ True - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs index 56e0303b0..c8b4d5a0a 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs @@ -5,26 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using NodaTime; -using Squidex.Infrastructure; - namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { - public sealed class EnrichedAssetEvent : EnrichedEvent + public sealed class EnrichedAssetEvent : EnrichedEntityEvent { public EnrichedAssetEventType Type { get; set; } - public Guid Id { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - public string MimeType { get; set; } public string FileName { get; set; } @@ -38,10 +24,5 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents public int? PixelWidth { get; set; } public int? PixelHeight { get; set; } - - public override Guid AggregateId - { - get { return Id; } - } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs index 88af9945c..a381d4c74 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs @@ -5,10 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using NodaTime; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { @@ -16,23 +13,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { public EnrichedContentEventType Type { get; set; } - public Guid Id { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - public NamedContentData Data { get; set; } public Status Status { get; set; } - - public override Guid AggregateId - { - get { return Id; } - } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs new file mode 100644 index 000000000..3293e293c --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEntityEvent.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using NodaTime; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +{ + public abstract class EnrichedEntityEvent : EnrichedEvent + { + public Guid Id { get; set; } + + public Instant Created { get; set; } + + public Instant LastModified { get; set; } + + public RefToken CreatedBy { get; set; } + + public RefToken LastModifiedBy { get; set; } + + public override void CalculatePartition() + { + Partition = Id.GetHashCode(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs index 0ca18fdf1..02929c715 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs @@ -25,10 +25,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents public long Version { get; set; } - [IgnoreDataMember] - public abstract Guid AggregateId { get; } + public long Partition { get; set; } [IgnoreDataMember] public IUser User { get; set; } + + public abstract void CalculatePartition(); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs index 528121c3b..824fac4af 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { - public abstract class EnrichedSchemaEvent : EnrichedEvent + public abstract class EnrichedSchemaEvent : EnrichedEntityEvent { public NamedId SchemaId { get; set; } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs index 9dc21c0e1..47351b4f5 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs @@ -6,9 +6,8 @@ // ========================================================================== using System; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Events; -using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Core.HandleRules { @@ -16,6 +15,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules { Type TriggerType { get; } - bool Triggers(Envelope @event, RuleTrigger trigger); + bool Triggers(EnrichedEvent @event, RuleTrigger trigger); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index 8f3bb8919..54d9dbb9f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -12,6 +12,7 @@ using System.Text; using System.Text.RegularExpressions; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; @@ -21,7 +22,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules { public class RuleEventFormatter { - private const string Undefined = "UNDEFINED"; + private const string Fallback = "null"; + private const string ScriptSuffix = ")"; + private const string ScriptPrefix = "Script("; private static readonly char[] ContentPlaceholderStartOld = "CONTENT_DATA".ToCharArray(); private static readonly char[] ContentPlaceholderStartNew = "{CONTENT_DATA".ToCharArray(); private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); @@ -29,13 +32,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules private readonly List<(char[] Pattern, Func Replacer)> patterns = new List<(char[] Pattern, Func Replacer)>(); private readonly IJsonSerializer jsonSerializer; private readonly IRuleUrlGenerator urlGenerator; + private readonly IScriptEngine scriptEngine; - public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator) + public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator, IScriptEngine scriptEngine) { Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); + Guard.NotNull(scriptEngine, nameof(scriptEngine)); Guard.NotNull(urlGenerator, nameof(urlGenerator)); this.jsonSerializer = jsonSerializer; + this.scriptEngine = scriptEngine; this.urlGenerator = urlGenerator; AddPattern("APP_ID", AppId); @@ -46,6 +52,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules AddPattern("SCHEMA_NAME", SchemaName); AddPattern("TIMESTAMP_DATETIME", TimestampTime); AddPattern("TIMESTAMP_DATE", TimestampDate); + AddPattern("USER_ID", UserId); AddPattern("USER_NAME", UserName); AddPattern("USER_EMAIL", UserEmail); } @@ -72,6 +79,19 @@ namespace Squidex.Domain.Apps.Core.HandleRules return text; } + if (text.StartsWith(ScriptPrefix, StringComparison.OrdinalIgnoreCase) && text.EndsWith(ScriptSuffix, StringComparison.OrdinalIgnoreCase)) + { + var script = text.Substring(ScriptPrefix.Length, text.Length - ScriptPrefix.Length - ScriptSuffix.Length); + + var customFunctions = new Dictionary> + { + ["contentUrl"] = () => ContentUrl(@event), + ["contentAction"] = () => ContentAction(@event) + }; + + return scriptEngine.Interpolate("event", @event, script, customFunctions); + } + var current = text.AsSpan(); var sb = new StringBuilder(); @@ -127,7 +147,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules } else { - sb.Append(Undefined); + sb.Append(Fallback); } current = current.Slice(match.Length + 1); @@ -169,7 +189,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules return schemaEvent.SchemaId.Id.ToString(); } - return Undefined; + return Fallback; } private static string SchemaName(EnrichedEvent @event) @@ -179,17 +199,17 @@ namespace Squidex.Domain.Apps.Core.HandleRules return schemaEvent.SchemaId.Name; } - return Undefined; + return Fallback; } private static string ContentAction(EnrichedEvent @event) { if (@event is EnrichedContentEvent contentEvent) { - return contentEvent.Type.ToString().ToLowerInvariant(); + return contentEvent.Type.ToString(); } - return Undefined; + return Fallback; } private string ContentUrl(EnrichedEvent @event) @@ -199,43 +219,22 @@ namespace Squidex.Domain.Apps.Core.HandleRules return urlGenerator.GenerateContentUIUrl(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id); } - return Undefined; + return Fallback; } private static string UserName(EnrichedEvent @event) { - if (@event.Actor != null) - { - if (@event.Actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase)) - { - return @event.Actor.ToString(); - } - - if (@event.User != null) - { - return @event.User.DisplayName(); - } - } + return @event.User?.DisplayName() ?? Fallback; + } - return Undefined; + private static string UserId(EnrichedEvent @event) + { + return @event.User?.Id ?? Fallback; } private static string UserEmail(EnrichedEvent @event) { - if (@event.Actor != null) - { - if (@event.Actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase)) - { - return @event.Actor.ToString(); - } - - if (@event.User != null) - { - return @event.User.Email; - } - } - - return Undefined; + return @event.User?.Email ?? Fallback; } private static string CalculateData(NamedContentData data, Match match) @@ -251,12 +250,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules if (!data.TryGetValue(path[0], out var field)) { - return Undefined; + return Fallback; } if (!field.TryGetValue(path[1], out var value)) { - return Undefined; + return Fallback; } for (var j = 2; j < path.Length; j++) @@ -272,16 +271,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules } else { - return Undefined; + return Fallback; } } if (value == null || value.Type == JsonValueType.Null) { - return Undefined; + return Fallback; } - return value.ToString() ?? Undefined; + return value.ToString() ?? Fallback; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index d6f4fa5a3..9b3eca324 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -85,7 +85,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules var appEventEnvelope = @event.To(); - if (!triggerHandler.Triggers(appEventEnvelope, rule.Trigger)) + var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope); + + if (!triggerHandler.Triggers(enrichedEvent, rule.Trigger)) { return null; } @@ -104,22 +106,22 @@ namespace Squidex.Domain.Apps.Core.HandleRules return null; } - var enrichedEvent = await eventEnricher.EnrichAsync(appEventEnvelope); - var actionName = typeNameRegistry.GetName(actionType); var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action); var json = jsonSerializer.Serialize(actionData.Data); + enrichedEvent.CalculatePartition(); + var job = new RuleJob { JobId = Guid.NewGuid(), ActionName = actionName, ActionData = json, - AggregateId = enrichedEvent.AggregateId, AppId = appEvent.AppId.Id, Created = now, EventName = enrichedEvent.Name, + ExecutionPartition = enrichedEvent.Partition, Expires = expires, Description = actionData.Description }; diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs index 18cd83b02..1f881f586 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs @@ -6,9 +6,8 @@ // ========================================================================== using System; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Events; -using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Core.HandleRules { @@ -19,11 +18,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules get { return typeof(T); } } - bool IRuleTriggerHandler.Triggers(Envelope @event, RuleTrigger trigger) + bool IRuleTriggerHandler.Triggers(EnrichedEvent @event, RuleTrigger trigger) { return Triggers(@event, (T)trigger); } - protected abstract bool Triggers(Envelope @event, T trigger); + protected abstract bool Triggers(EnrichedEvent @event, T trigger); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs index 7df652e50..6132ac557 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/AssetChangedTriggerHandler.cs @@ -5,27 +5,32 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.HandleRules.Triggers { - public sealed class AssetChangedTriggerHandler : RuleTriggerHandler + public sealed class AssetChangedTriggerHandler : RuleTriggerHandler { - protected override bool Triggers(Envelope @event, AssetChangedTrigger trigger) + private readonly IScriptEngine scriptEngine; + + public AssetChangedTriggerHandler(IScriptEngine scriptEngine) + { + Guard.NotNull(scriptEngine, nameof(scriptEngine)); + + this.scriptEngine = scriptEngine; + } + + protected override bool Triggers(EnrichedEvent @event, AssetChangedTriggerV2 trigger) { - return @event.Payload is AssetEvent assetEvent && MatchsType(trigger, assetEvent); + return @event is EnrichedAssetEvent assetEvent && MatchsType(trigger, assetEvent); } - private static bool MatchsType(AssetChangedTrigger trigger, AssetEvent @event) + private bool MatchsType(AssetChangedTriggerV2 trigger, EnrichedAssetEvent assetEvent) { - return - trigger.SendCreate && @event is AssetCreated || - trigger.SendUpdate && @event is AssetUpdated || - trigger.SendDelete && @event is AssetDeleted || - trigger.SendRename && @event is AssetRenamed; + return string.IsNullOrWhiteSpace(trigger.Condition) || scriptEngine.Evaluate("event", assetEvent, trigger.Condition); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs index f03100f98..fc2e84891 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs @@ -5,32 +5,36 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.HandleRules.Triggers { - public sealed class ContentChangedTriggerHandler : RuleTriggerHandler + public sealed class ContentChangedTriggerHandler : RuleTriggerHandler { - protected override bool Triggers(Envelope @event, ContentChangedTrigger trigger) + private readonly IScriptEngine scriptEngine; + + public ContentChangedTriggerHandler(IScriptEngine scriptEngine) { - if (trigger.HandleAll && - @event.Payload is ContentEvent && - !(@event.Payload is ContentChangesPublished) && - !(@event.Payload is ContentChangesDiscarded) && - !(@event.Payload is ContentUpdateProposed)) + Guard.NotNull(scriptEngine, nameof(scriptEngine)); + + this.scriptEngine = scriptEngine; + } + + protected override bool Triggers(EnrichedEvent @event, ContentChangedTriggerV2 trigger) + { + if (trigger.HandleAll) { return true; } - if (trigger.Schemas != null && @event.Payload is SchemaEvent schemaEvent) + if (trigger.Schemas != null && @event is EnrichedSchemaEvent schemaEvent) { foreach (var schema in trigger.Schemas) { - if (MatchsSchema(schema, schemaEvent) && MatchsType(schema, schemaEvent)) + if (MatchsSchema(schema, schemaEvent) && MatchsCondition(schema, schemaEvent)) { return true; } @@ -40,56 +44,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers return false; } - private static bool MatchsSchema(ContentChangedTriggerSchema schema, SchemaEvent @event) + private bool MatchsSchema(ContentChangedTriggerSchemaV2 schema, EnrichedSchemaEvent @event) { return @event.SchemaId.Id == schema.SchemaId; } - private static bool MatchsType(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return - IsArchived(schema, @event) || - IsCreate(schema, @event) || - IsDelete(schema, @event) || - IsPublished(schema, @event) || - IsRestored(schema, @event) || - IsUpdate(schema, @event) || - IsUnpublished(schema, @event); - } - - private static bool IsPublished(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Published; - } - - private static bool IsRestored(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendRestore && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Restored; - } - - private static bool IsArchived(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendArchived && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Archived; - } - - private static bool IsUnpublished(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendUnpublish && @event is ContentStatusChanged statusChanged && statusChanged.Change == StatusChange.Unpublished; - } - - private static bool IsCreate(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendCreate && @event is ContentCreated; - } - - private static bool IsUpdate(ContentChangedTriggerSchema schema, SchemaEvent @event) - { - return schema.SendUpdate && @event is ContentUpdated || schema.SendUpdate && @event is ContentChangesPublished; - } - - private static bool IsDelete(ContentChangedTriggerSchema schema, SchemaEvent @event) + private bool MatchsCondition(ContentChangedTriggerSchemaV2 schema, EnrichedSchemaEvent @event) { - return schema.SendDelete && @event is ContentDeleted; + return string.IsNullOrWhiteSpace(schema.Condition) || scriptEngine.Evaluate("event", @event, schema.Condition); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs index 6a72f9508..e75e1f0b8 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { private readonly NamedContentData contentData; private HashSet fieldsToDelete; - private Dictionary fieldProperties; + private Dictionary fieldProperties; private bool isChanged; public ContentDataObject(Engine engine, NamedContentData contentData) @@ -55,7 +55,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { foreach (var kvp in fieldProperties) { - if (kvp.Value.ContentField.TryUpdate(out var fieldData)) + var value = (ContentDataProperty)kvp.Value; + + if (value.ContentField.TryUpdate(out var fieldData)) { contentData[kvp.Key] = fieldData; } @@ -109,17 +111,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { EnsurePropertiesInitialized(); - foreach (var property in fieldProperties) - { - yield return new KeyValuePair(property.Key, property.Value); - } + return fieldProperties; } private void EnsurePropertiesInitialized() { if (fieldProperties == null) { - fieldProperties = new Dictionary(contentData.Count); + fieldProperties = new Dictionary(contentData.Count); foreach (var kvp in contentData) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs index 4ea31b9ea..b9a51cfbf 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataProperty.cs @@ -7,18 +7,17 @@ using Jint.Native; using Jint.Runtime; -using Jint.Runtime.Descriptors; using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { - public sealed class ContentDataProperty : PropertyDescriptor + public sealed class ContentDataProperty : CustomProperty { private readonly ContentDataObject contentData; private ContentFieldObject contentField; private JsValue value; - public override JsValue Value + protected override JsValue CustomValue { get { @@ -42,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper contentField.Put(kvp.Key, kvp.Value.Value, true); } - this.value = new JsValue(contentField); + this.value = contentField; } } } @@ -53,14 +52,13 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper } public ContentDataProperty(ContentDataObject contentData, ContentFieldObject contentField = null) - : base(null, true, true, true) { this.contentData = contentData; this.contentField = contentField; if (contentField != null) { - value = new JsValue(contentField); + value = contentField; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs index f92963096..d6ef06266 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper private readonly ContentDataObject contentData; private readonly ContentFieldData fieldData; private HashSet valuesToDelete; - private Dictionary valueProperties; + private Dictionary valueProperties; private bool isChanged; public ContentFieldData FieldData @@ -67,9 +67,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { foreach (var kvp in valueProperties) { - if (kvp.Value.IsChanged) + var value = (ContentFieldProperty)kvp.Value; + + if (value.IsChanged) { - fieldData[kvp.Key] = kvp.Value.ContentValue; + fieldData[kvp.Key] = value.ContentValue; } } } @@ -114,17 +116,14 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { EnsurePropertiesInitialized(); - foreach (var property in valueProperties) - { - yield return new KeyValuePair(property.Key, property.Value); - } + return valueProperties; } private void EnsurePropertiesInitialized() { if (valueProperties == null) { - valueProperties = new Dictionary(FieldData.Count); + valueProperties = new Dictionary(FieldData.Count); foreach (var kvp in FieldData) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs index ee279b40a..ed5aa34d2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs @@ -6,19 +6,18 @@ // ========================================================================== using Jint.Native; -using Jint.Runtime.Descriptors; using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { - public sealed class ContentFieldProperty : PropertyDescriptor + public sealed class ContentFieldProperty : CustomProperty { private readonly ContentFieldObject contentField; private IJsonValue contentValue; private JsValue value; private bool isChanged; - public override JsValue Value + protected override JsValue CustomValue { get { @@ -49,7 +48,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper } public ContentFieldProperty(ContentFieldObject contentField, IJsonValue contentValue = null) - : base(null, true, true, true) { this.contentField = contentField; this.contentValue = contentValue; diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/CustomProperty.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/CustomProperty.cs new file mode 100644 index 000000000..bd1230537 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/CustomProperty.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Jint.Runtime.Descriptors; + +namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper +{ + public abstract class CustomProperty : PropertyDescriptor + { + public CustomProperty() + : base(PropertyFlag.CustomJsValue) + { + Enumerable = true; + + Writable = true; + + Configurable = true; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs index ec9d52054..33e5fd891 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs @@ -27,11 +27,11 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper case JsonNull n: return JsValue.Null; case JsonScalar s: - return new JsValue(s.Value); + return new JsString(s.Value); case JsonScalar b: - return new JsValue(b.Value); + return new JsBoolean(b.Value); case JsonScalar b: - return new JsValue(b.Value); + return new JsNumber(b.Value); case JsonObject obj: return FromObject(obj, engine); case JsonArray arr: diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs index e67acdcb6..55742c99d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; +using System.Collections.Generic; using Squidex.Domain.Apps.Core.Contents; namespace Squidex.Domain.Apps.Core.Scripting @@ -16,5 +18,9 @@ namespace Squidex.Domain.Apps.Core.Scripting NamedContentData ExecuteAndTransform(ScriptContext context, string script); NamedContentData Transform(ScriptContext context, string script); + + bool Evaluate(string name, object context, string script); + + string Interpolate(string name, object context, string script, Dictionary> customFormatters = null); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs index 890b9c26c..35cc085ac 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs @@ -6,15 +6,20 @@ // ========================================================================== using System; +using System.Collections.Generic; +using System.Globalization; +using System.Security.Claims; using Jint; using Jint.Native; +using Jint.Native.Date; using Jint.Native.Object; -using Jint.Parser; using Jint.Runtime; using Jint.Runtime.Interop; +using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; using Squidex.Infrastructure; +using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Core.Scripting { @@ -22,6 +27,40 @@ namespace Squidex.Domain.Apps.Core.Scripting { public TimeSpan Timeout { get; set; } = TimeSpan.FromMilliseconds(200); + public sealed class Converter : IObjectConverter + { + public Engine Engine { get; set; } + + public bool TryConvert(object value, out JsValue result) + { + result = null; + + if (value is Enum) + { + result = value.ToString(); + return true; + } + + switch (value) + { + case IUser user: + result = JintUser.Create(Engine, user); + return true; + case ClaimsPrincipal principal: + result = JintUser.Create(Engine, principal); + return true; + case Instant instant: + result = JsValue.FromObject(Engine, instant.ToDateTimeUtc()); + return true; + case NamedContentData content: + result = new ContentDataObject(Engine, content); + return true; + } + + return false; + } + } + public void Execute(ScriptContext context, string script) { Guard.NotNull(context, nameof(context)); @@ -115,7 +154,7 @@ namespace Squidex.Domain.Apps.Core.Scripting { engine.Execute(script); } - catch (ParserException ex) + catch (ArgumentException ex) { throw new ValidationException($"Failed to execute script with javascript syntax error: {ex.Message}", new ValidationError(ex.Message)); } @@ -127,7 +166,7 @@ namespace Squidex.Domain.Apps.Core.Scripting private Engine CreateScriptEngine(ScriptContext context) { - var engine = new Engine(options => options.TimeoutInterval(Timeout).Strict()); + var engine = CreateScriptEngine(); var contextInstance = new ObjectInstance(engine); @@ -143,7 +182,7 @@ namespace Squidex.Domain.Apps.Core.Scripting if (context.User != null) { - contextInstance.FastAddProperty("user", new JintUser(engine, context.User), false, true, false); + contextInstance.FastAddProperty("user", JintUser.Create(engine, context.User), false, true, false); } if (!string.IsNullOrWhiteSpace(context.Operation)) @@ -152,12 +191,57 @@ namespace Squidex.Domain.Apps.Core.Scripting } engine.SetValue("ctx", contextInstance); + engine.SetValue("context", contextInstance); + + return engine; + } + + private Engine CreateScriptEngine(IReferenceResolver resolver = null, Dictionary> customFormatters = null) + { + var converter = new Converter(); + + var engine = new Engine(options => + { + if (resolver != null) + { + options.SetReferencesResolver(resolver); + } - engine.SetValue("slugify", new ClrFunctionInstance(engine, Slugify)); + options.TimeoutInterval(Timeout).Strict().AddObjectConverter(converter); + }); + + if (customFormatters != null) + { + foreach (var kvp in customFormatters) + { + engine.SetValue(kvp.Key, Safe(kvp.Value)); + } + } + + converter.Engine = engine; + + engine.SetValue("slugify", new ClrFunctionInstance(engine, "slugify", Slugify)); + engine.SetValue("formatTime", new ClrFunctionInstance(engine, "formatTime", FormatDate)); + engine.SetValue("formatDate", new ClrFunctionInstance(engine, "formatDate", FormatDate)); return engine; } + private static Func Safe(Func func) + { + return new Func(() => + { + try + { + return func(); + } + catch + { + return "null"; + } + }); + } + private static JsValue Slugify(JsValue thisObject, JsValue[] arguments) { try @@ -178,6 +262,21 @@ namespace Squidex.Domain.Apps.Core.Scripting } } + private static JsValue FormatDate(JsValue thisObject, JsValue[] arguments) + { + try + { + var dateValue = ((DateInstance)arguments.At(0)).ToDateTime(); + var dateFormat = TypeConverter.ToString(arguments.At(1)); + + return dateValue.ToString(dateFormat, CultureInfo.InvariantCulture); + } + catch + { + return JsValue.Undefined; + } + } + private static void EnableDisallow(Engine engine) { engine.SetValue("disallow", new Action(message => @@ -197,5 +296,45 @@ namespace Squidex.Domain.Apps.Core.Scripting throw new ValidationException("Script rejected the operation.", errors); })); } + + public bool Evaluate(string name, object context, string script) + { + try + { + var result = + CreateScriptEngine(NullPropagation.Instance) + .SetValue(name, context) + .Execute(script) + .GetCompletionValue() + .ToObject(); + + return (bool)result; + } + catch + { + return false; + } + } + + public string Interpolate(string name, object context, string script, Dictionary> customFormatters = null) + { + try + { + var result = + CreateScriptEngine(NullPropagation.Instance, customFormatters) + .SetValue(name, context) + .Execute(script) + .GetCompletionValue() + .ToObject(); + + var converted = result.ToString(); + + return converted == "undefined" ? "null" : converted; + } + catch (Exception ex) + { + return ex.Message; + } + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs index 36094bd11..3d2177d4b 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs @@ -5,21 +5,31 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Linq; using System.Security.Claims; using Jint; -using Jint.Native; -using Jint.Native.Object; +using Jint.Runtime.Interop; using Squidex.Infrastructure.Security; +using Squidex.Shared.Identity; +using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Core.Scripting { - public sealed class JintUser : ObjectInstance + public static class JintUser { private static readonly char[] ClaimSeparators = { '/', '.', ':' }; - public JintUser(Engine engine, ClaimsPrincipal principal) - : base(engine) + public static ObjectWrapper Create(Engine engine, IUser user) + { + var clientId = user.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.ClientId)?.Value; + + var isClient = !string.IsNullOrWhiteSpace(clientId); + + return CreateUser(engine, user.Id, isClient, user.Email, user.DisplayName(), user.Claims); + } + + public static ObjectWrapper Create(Engine engine, ClaimsPrincipal principal) { var id = principal.OpenIdSubject(); @@ -30,22 +40,20 @@ namespace Squidex.Domain.Apps.Core.Scripting id = principal.OpenIdClientId(); } - FastAddProperty("id", id, false, true, false); - FastAddProperty("isClient", isClient, false, true, false); - - FastAddProperty("email", principal.OpenIdEmail(), false, true, false); + var name = principal.FindFirst(SquidexClaimTypes.DisplayName)?.Value; - var claimsInstance = new ObjectInstance(engine); - - foreach (var group in principal.Claims.GroupBy(x => x.Type)) - { - var propertyName = group.Key.Split(ClaimSeparators).Last(); - var propertyValue = engine.Array.Construct(group.Select(x => new JsValue(x.Value)).ToArray()); + return CreateUser(engine, id, isClient, principal.OpenIdEmail(), name, principal.Claims); + } - claimsInstance.FastAddProperty(propertyName, propertyValue, false, true, false); - } + private static ObjectWrapper CreateUser(Engine engine, string id, bool isClient, string email, string name, IEnumerable allClaims) + { + var claims = + allClaims.GroupBy(x => x.Type) + .ToDictionary( + x => x.Key.Split(ClaimSeparators).Last(), + x => x.Select(y => y.Value).ToArray()); - FastAddProperty("claims", claimsInstance, false, true, false); + return new ObjectWrapper(engine, new { id, isClient, email, name, claims }); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs new file mode 100644 index 000000000..adc3d577b --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/NullPropagation.cs @@ -0,0 +1,43 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Jint; +using Jint.Native; +using Jint.Runtime.Interop; +using Jint.Runtime.References; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + public sealed class NullPropagation : IReferenceResolver + { + public static readonly NullPropagation Instance = new NullPropagation(); + + public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value) + { + value = reference.GetBase(); + + return true; + } + + public bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value) + { + return value.IsNull() || value.IsUndefined(); + } + + public bool TryGetCallable(Engine engine, object reference, out JsValue value) + { + value = new ClrFunctionInstance(engine, "anonymous", (thisObj, values) => thisObj); + + return true; + } + + public bool CheckCoercible(JsValue value) + { + return true; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index 73e3a75a6..95b5cdc62 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj index 1b1b62890..23096e86d 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs index c2a126881..e49411069 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs @@ -170,14 +170,22 @@ namespace Squidex.Domain.Apps.Entities.Rules { x.AbsoluteExpirationRelativeToNow = UserCacheDuration; + IUser user = null; try { - return await userResolver.FindByIdOrEmailAsync(actor.Identifier); + user = await userResolver.FindByIdOrEmailAsync(actor.Identifier); } catch { - return null; + user = null; } + + if (user == null && actor.Type.Equals(RefTokenType.Client, StringComparison.OrdinalIgnoreCase)) + { + user = new ClientUser(actor); + } + + return user; }); } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs index ea513751d..1e50e9606 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs @@ -35,12 +35,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards return action.Accept(visitor); } - public Task> Visit(AssetChangedTrigger trigger) + public Task> Visit(AssetChangedTriggerV2 trigger) { return Task.FromResult(Enumerable.Empty()); } - public async Task> Visit(ContentChangedTrigger trigger) + public async Task> Visit(ContentChangedTriggerV2 trigger) { if (trigger.Schemas != null) { diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs index 7ec68c1ee..5dc76d7ca 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuerGrain.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Rules this.log = log; requestBlock = - new PartitionedActionBlock(HandleAsync, x => x.Job.AggregateId.GetHashCode(), + new PartitionedActionBlock(HandleAsync, x => x.Job.ExecutionPartition, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); } diff --git a/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 227f17c93..58839d32f 100644 --- a/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -15,12 +15,12 @@ - + all runtime; build; native; contentfiles; analyzers - - + + diff --git a/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs b/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs index bab06a970..ff5cdbd95 100644 --- a/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs +++ b/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs @@ -6,15 +6,26 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Rules { [EventType(nameof(RuleCreated))] - public sealed class RuleCreated : RuleEvent + public sealed class RuleCreated : RuleEvent, IMigrated { public RuleTrigger Trigger { get; set; } public RuleAction Action { get; set; } + + public IEvent Migrate() + { + if (Trigger is IMigrated migrated) + { + Trigger = migrated.Migrate(); + } + + return this; + } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Events/Rules/RuleUpdated.cs b/src/Squidex.Domain.Apps.Events/Rules/RuleUpdated.cs index 250710106..589486c54 100644 --- a/src/Squidex.Domain.Apps.Events/Rules/RuleUpdated.cs +++ b/src/Squidex.Domain.Apps.Events/Rules/RuleUpdated.cs @@ -6,15 +6,26 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Rules { [EventType(nameof(RuleUpdated))] - public sealed class RuleUpdated : RuleEvent + public sealed class RuleUpdated : RuleEvent, IMigrated { public RuleTrigger Trigger { get; set; } public RuleAction Action { get; set; } + + public IEvent Migrate() + { + if (Trigger is IMigrated migrated) + { + Trigger = migrated.Migrate(); + } + + return this; + } } } diff --git a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj index e679181fb..0e58689d7 100644 --- a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj +++ b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs b/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs index 8135566cf..bbf07c80e 100644 --- a/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs +++ b/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs @@ -30,7 +30,7 @@ namespace Squidex.Infrastructure.EventSourcing var payloadType = typeNameRegistry.GetType(eventData.Type); var payloadObj = serializer.Deserialize(eventData.Payload, payloadType, stringConverter); - if (payloadObj is IMigratedEvent migratedEvent) + if (payloadObj is IMigrated migratedEvent) { payloadObj = migratedEvent.Migrate(); } @@ -44,7 +44,7 @@ namespace Squidex.Infrastructure.EventSourcing { var eventPayload = envelope.Payload; - if (migrate && eventPayload is IMigratedEvent migratedEvent) + if (migrate && eventPayload is IMigrated migratedEvent) { eventPayload = migratedEvent.Migrate(); } diff --git a/src/Squidex.Infrastructure/EventSourcing/IMigratedEvent.cs b/src/Squidex.Infrastructure/IMigrated.cs similarity index 78% rename from src/Squidex.Infrastructure/EventSourcing/IMigratedEvent.cs rename to src/Squidex.Infrastructure/IMigrated.cs index 2b8c79785..c2f74a40b 100644 --- a/src/Squidex.Infrastructure/EventSourcing/IMigratedEvent.cs +++ b/src/Squidex.Infrastructure/IMigrated.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Infrastructure.EventSourcing +namespace Squidex.Infrastructure { - public interface IMigratedEvent + public interface IMigrated { - IEvent Migrate(); + T Migrate(); } } diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 42e0bc9be..2be4be747 100644 --- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -11,15 +11,15 @@ - - + + all runtime; build; native; contentfiles; analyzers - - + + - + diff --git a/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs b/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs index a649ce53f..2f61604c8 100644 --- a/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs +++ b/src/Squidex.Infrastructure/Tasks/PartitionedActionBlock.cs @@ -23,22 +23,22 @@ namespace Squidex.Infrastructure.Tasks get { return Task.WhenAll(workers.Select(x => x.Completion)); } } - public PartitionedActionBlock(Action action, Func partitioner) + public PartitionedActionBlock(Action action, Func partitioner) : this (action?.ToAsync(), partitioner, new ExecutionDataflowBlockOptions()) { } - public PartitionedActionBlock(Func action, Func partitioner) + public PartitionedActionBlock(Func action, Func partitioner) : this(action, partitioner, new ExecutionDataflowBlockOptions()) { } - public PartitionedActionBlock(Action action, Func partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions) + public PartitionedActionBlock(Action action, Func partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions) : this(action?.ToAsync(), partitioner, dataflowBlockOptions) { } - public PartitionedActionBlock(Func action, Func partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions) + public PartitionedActionBlock(Func action, Func partitioner, ExecutionDataflowBlockOptions dataflowBlockOptions) { Guard.NotNull(action, nameof(action)); Guard.NotNull(partitioner, nameof(partitioner)); diff --git a/src/Squidex.Shared/Users/ClientUser.cs b/src/Squidex.Shared/Users/ClientUser.cs new file mode 100644 index 000000000..88f840eff --- /dev/null +++ b/src/Squidex.Shared/Users/ClientUser.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Security.Claims; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Security; +using Squidex.Shared.Identity; + +namespace Squidex.Shared.Users +{ + public sealed class ClientUser : IUser + { + private readonly RefToken token; + private readonly List claims; + + public ClientUser(RefToken token) + { + Guard.NotNull(token, nameof(token)); + + this.token = token; + + claims = new List + { + new Claim(OpenIdClaims.ClientId, token.Identifier), + new Claim(SquidexClaimTypes.DisplayName, token.ToString()) + }; + } + + public string Id + { + get { return token.Identifier; } + } + + public string Email + { + get { return token.ToString(); } + } + + public bool IsLocked + { + get { return false; } + } + + public IReadOnlyList Claims + { + get { return claims; } + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs index cf2c3c4a2..ab6339b9d 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs @@ -26,12 +26,12 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters return properties.Accept(Instance); } - public RuleTriggerDto Visit(AssetChangedTrigger trigger) + public RuleTriggerDto Visit(AssetChangedTriggerV2 trigger) { return SimpleMapper.Map(trigger, new AssetChangedRuleTriggerDto()); } - public RuleTriggerDto Visit(ContentChangedTrigger trigger) + public RuleTriggerDto Visit(ContentChangedTriggerV2 trigger) { var schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedRuleTriggerSchemaDto())).ToArray(); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs index 4a499124d..c0c7c8899 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/AssetChangedRuleTriggerDto.cs @@ -14,28 +14,13 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers public sealed class AssetChangedRuleTriggerDto : RuleTriggerDto { /// - /// Determines whether to handle the event when an asset is created. + /// Javascript condition when to trigger. /// - public bool SendCreate { get; set; } - - /// - /// Determines whether to handle the event when an asset is updated. - /// - public bool SendUpdate { get; set; } - - /// - /// Determines whether to handle the event when an asset is renamed. - /// - public bool SendRename { get; set; } - - /// - /// Determines whether to handle the event when an asset is deleted. - /// - public bool SendDelete { get; set; } + public string Condition { get; set; } public override RuleTrigger ToTrigger() { - return SimpleMapper.Map(this, new AssetChangedTrigger()); + return SimpleMapper.Map(this, new AssetChangedTriggerV2()); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerDto.cs index 107c7d548..dbf304a39 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerDto.cs @@ -29,9 +29,9 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers public override RuleTrigger ToTrigger() { - var schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToReadOnlyCollection(); + var schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaV2())).ToReadOnlyCollection(); - return new ContentChangedTrigger { HandleAll = HandleAll, Schemas = schemas }; + return new ContentChangedTriggerV2 { HandleAll = HandleAll, Schemas = schemas }; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs index 9570a5182..93c05c61d 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedRuleTriggerSchemaDto.cs @@ -17,38 +17,8 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers public Guid SchemaId { get; set; } /// - /// Determines whether to handle the event when a content is created. + /// Javascript condition when to trigger. /// - public bool SendCreate { get; set; } - - /// - /// Determines whether to handle the event when a content is updated. - /// - public bool SendUpdate { get; set; } - - /// - /// Determines whether to handle the event when a content is deleted. - /// - public bool SendDelete { get; set; } - - /// - /// Determines whether to handle the event when a content is published. - /// - public bool SendPublish { get; set; } - - /// - /// Determines whether to handle the event when a content is unpublished. - /// - public bool SendUnpublish { get; set; } - - /// - /// Determines whether to handle the event when a content is archived. - /// - public bool SendArchived { get; set; } - - /// - /// Determines whether to handle the event when a content is restored. - /// - public bool SendRestore { get; set; } + public string Condition { get; set; } } } diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 750f8bdde..40fab103a 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -54,7 +54,7 @@ - + @@ -74,14 +74,14 @@ - - - - - + + + + + - - + + diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html index 3f5c593e8..e566d4724 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html +++ b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.html @@ -1,34 +1,33 @@
-
- - -
+ + + + +
-
-
- - -
-
-
-
- - -
-
-
-
- - -
+ +
+

Conditions

+ +

Conditions are javascript expressions that define when to trigger, for example:

+ +
    +
  • + Specific events: + +
    event.type == 'Created' || event.type == 'Updated'
    +
  • +
  • + Large assets: + +
    event.fileSize > 100000000
    +
  • +
  • + Images only: + +
    event.isImage
    +
  • +
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.scss b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.scss index fbb752506..fa39a4f95 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.scss @@ -1,2 +1,33 @@ @import '_vars'; -@import '_mixins'; \ No newline at end of file +@import '_mixins'; + +.code { + font-family: monospace; +} + +textarea { + height: 100px; +} + +pre { + background: $color-border; + border: 0; + padding: .25rem .5rem; + margin: 0; +} + +.help { + & { + font-size: .9rem; + } + + &-example { + margin-top: .375rem; + } + + &-examples { + margin-top: 0; + margin-bottom: 0; + padding-left: 1.5rem; + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts index 130dd0dab..2c8138bb3 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/asset-changed-trigger.component.ts @@ -24,16 +24,7 @@ export class AssetChangedTriggerComponent implements OnInit { public triggerFormSubmitted = false; public ngOnInit() { - this.triggerForm.setControl('sendCreate', - new FormControl(this.trigger.sendCreate || false)); - - this.triggerForm.setControl('sendUpdate', - new FormControl(this.trigger.sendUpdate || false)); - - this.triggerForm.setControl('sendRename', - new FormControl(this.trigger.sendRename || false)); - - this.triggerForm.setControl('sendDelete', - new FormControl(this.trigger.sendDelete || false)); + this.triggerForm.setControl('condition', + new FormControl(this.trigger.condition || '')); } } \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html index 1b62cf7c3..7dacf15fb 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html @@ -1,15 +1,8 @@ - - - - - - - - - + + @@ -17,29 +10,8 @@ - - - - - - - - @@ -48,29 +20,8 @@ - - - - - - - -
Schema -
All
-
-
Created
-
-
Updated
-
-
Deleted
-
-
Published
-
-
Unpublished
-
-
Archived
-
-
Restored
+
+ Condition
{{schema.schema.displayName}} - - - - - - - - - - - - - - - + + + + +
+

Conditions

+ +

Conditions are javascript expressions that define when to trigger, for example:

+ +
    +
  • + Specific events: + +
    event.type == 'Created' || event.type == 'Published'
    +
  • +
  • + Content has value: + +
    event.data.important.iv === true
    +
  • +
  • + Updated by user: + +
    user.email === 'user@squidex.io'
    +
  • +
+
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.scss b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.scss index 62d9b45b4..9ccd3ed9e 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.scss @@ -4,17 +4,40 @@ .section { border-top: 1px solid $color-border; padding-top: 1rem; - padding-bottom: 0; -} - -.table { - margin-top: 3.5rem; + padding-bottom: 1rem; } .form-check { margin-top: 1rem; } -.rotated-label { - @include rotate(-45deg); +.code { + font-family: monospace; +} + +textarea { + height: 100px; +} + +pre { + background: $color-border; + border: 0; + padding: .25rem .5rem; + margin: 0; +} + +.help { + & { + font-size: .9rem; + } + + &-example { + margin-top: .375rem; + } + + &-examples { + margin-top: 0; + margin-bottom: 0; + padding-left: 1.5rem; + } } \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index ba2eaad1d..60e69a83c 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -16,14 +16,8 @@ import { export interface TriggerSchemaForm { schema: SchemaDto; - sendAll: boolean; - sendCreate: boolean; - sendUpdate: boolean; - sendDelete: boolean; - sendPublish: boolean; - sendUnpublish: boolean; - sendArchive: boolean; - sendRestore: boolean; + + condition?: string; } @Component({ @@ -60,125 +54,45 @@ export class ContentChangedTriggerComponent implements OnInit { this.triggerForm.setControl('handleAll', new FormControl(Types.isBoolean(this.trigger.handleAll) ? this.trigger.handleAll : false)); - const triggerSchemas: any[] = (this.trigger.schemas = this.trigger.schemas || []); - - this.triggerSchemas = - ImmutableArray.of( - triggerSchemas.map(triggerSchema => { - const schema = this.schemas.find(s => s.id === triggerSchema.schemaId); - - if (schema) { - return this.updateSendAll({ - schema: schema, - sendAll: false, - sendCreate: triggerSchema.sendCreate, - sendUpdate: triggerSchema.sendUpdate, - sendDelete: triggerSchema.sendDelete, - sendPublish: triggerSchema.sendPublish, - sendUnpublish: triggerSchema.sendUnpublish, - sendArchive: triggerSchema.sendArchive, - sendRestore: triggerSchema.sendRestore - }); - } else { - return null; - } - }).filter(s => !!s).map(s => s!)).sortByStringAsc(s => s.schema.name); - - this.schemasToAdd = - this.schemas.filter(schema => - !triggerSchemas.find(s => s.schemaId === schema.id)) - .sortByStringAsc(x => x.name); - - this.schemaToAdd = this.schemasToAdd.at(0); - } + const schemas: TriggerSchemaForm[] = []; - public removeSchema(schemaForm: TriggerSchemaForm) { - this.triggerSchemas = this.triggerSchemas.remove(schemaForm); - - this.updateValue(); - - this.schemasToAdd = this.schemasToAdd.push(schemaForm.schema).sortByStringAsc(x => x.name); - this.schemaToAdd = this.schemasToAdd.at(0); - } + if (this.trigger.schemas) { + for (let triggerSchema of this.trigger.schemas) { + const schema = this.schemas.find(s => s.id === triggerSchema.schemaId); - public addSchema() { - this.triggerSchemas = - this.triggerSchemas.push( - this.updateSendAll({ - schema: this.schemaToAdd, - sendAll: false, - sendCreate: false, - sendUpdate: false, - sendDelete: false, - sendPublish: false, - sendUnpublish: false, - sendArchive: false, - sendRestore: false - })).sortByStringAsc(x => x.schema.name); + if (schema) { + schemas.push({ schema, condition: triggerSchema.condition }); + } + } + } - this.updateValue(); + this.triggerSchemas = ImmutableArray.of(schemas).sortByStringAsc(s => s.schema.name); - this.schemasToAdd = this.schemasToAdd.remove(this.schemaToAdd).sortByStringAsc(x => x.name); - this.schemaToAdd = this.schemasToAdd.at(0); + this.updateSchemaToAdd(); } - public toggle(schemaForm: TriggerSchemaForm, property: string) { - const newSchema = this.updateSendAll(Object.assign({}, schemaForm, { [property]: !schemaForm[property] })); - - this.triggerSchemas = this.triggerSchemas.replace(schemaForm, newSchema); + public removeSchema(schemaForm: TriggerSchemaForm) { + this.triggerSchemas = this.triggerSchemas.remove(schemaForm); this.updateValue(); + this.updateSchemaToAdd(); } - public toggleAll(schemaForm: TriggerSchemaForm) { - const newSchema = this.updateAll({ schema: schemaForm.schema }, !schemaForm.sendAll); - - this.triggerSchemas = this.triggerSchemas.replace(schemaForm, newSchema); + public addSchema() { + this.triggerSchemas = this.triggerSchemas.push({ schema: this.schemaToAdd }).sortByStringAsc(x => x.schema.name); this.updateValue(); + this.updateSchemaToAdd(); } - private updateValue() { - const schemas = - this.triggerSchemas.values.map(s => { - return { - schemaId: s.schema.id, - sendCreate: s.sendCreate, - sendUpdate: s.sendUpdate, - sendDelete: s.sendDelete, - sendPublish: s.sendPublish, - sendUnpublish: s.sendUnpublish, - sendArchive: s.sendArchive, - sendRestore: s.sendRestore - }; - }); + public updateValue() { + const schemas = this.triggerSchemas.values.map(s => ({ schemaId: s.schema.id, condition: s.condition })); this.triggerForm.controls['schemas'].setValue(schemas); } - private updateAll(schemaForm: TriggerSchemaForm, value: boolean): TriggerSchemaForm { - schemaForm.sendAll = value; - schemaForm.sendCreate = value; - schemaForm.sendUpdate = value; - schemaForm.sendDelete = value; - schemaForm.sendPublish = value; - schemaForm.sendUnpublish = value; - schemaForm.sendArchive = value; - schemaForm.sendRestore = value; - - return schemaForm; - } - - private updateSendAll(schemaForm: TriggerSchemaForm): TriggerSchemaForm { - schemaForm.sendAll = - schemaForm.sendCreate && - schemaForm.sendUpdate && - schemaForm.sendDelete && - schemaForm.sendPublish && - schemaForm.sendUnpublish && - schemaForm.sendArchive && - schemaForm.sendRestore; - - return schemaForm; + private updateSchemaToAdd() { + this.schemasToAdd = this.schemas.filter(schema => !this.triggerSchemas.find(s => s.schema.id === schema.id)).sortByStringAsc(x => x.name); + this.schemaToAdd = this.schemasToAdd.at(0); } } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs index e9f155b52..225e4c8d5 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.Model.Rules .Select(x => new[] { x }) .ToList(); - private readonly Rule rule_0 = new Rule(new ContentChangedTrigger(), new TestAction1()); + private readonly Rule rule_0 = new Rule(new ContentChangedTriggerV2(), new TestAction1()); public sealed class OtherTrigger : RuleTrigger { @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.Model.Rules [Fact] public void Should_create_with_trigger_and_action() { - var ruleTrigger = new ContentChangedTrigger(); + var ruleTrigger = new ContentChangedTriggerV2(); var ruleAction = new TestAction1(); var newRule = new Rule(ruleTrigger, ruleAction); @@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.Model.Rules [Fact] public void Should_replace_trigger_when_updating() { - var newTrigger = new ContentChangedTrigger(); + var newTrigger = new ContentChangedTriggerV2(); var rule_1 = rule_0.Update(newTrigger); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs index 06226aa4d..019ae63f2 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -13,6 +13,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Shared.Identity; @@ -32,13 +33,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules public RuleEventFormatterTests() { + A.CallTo(() => user.Id) + .Returns("123"); + A.CallTo(() => user.Email) .Returns("me@email.com"); A.CallTo(() => user.Claims) .Returns(new List { new Claim(SquidexClaimTypes.DisplayName, "me") }); - sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator); + A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId)) + .Returns("content-url"); + + sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator, new JintScriptEngine()); } [Fact] @@ -69,91 +76,120 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules Assert.Contains("MyEventName", result); } - [Fact] - public void Should_replace_app_information_from_event() + [Theory] + [InlineData("Name $APP_NAME has id $APP_ID")] + [InlineData("Script(`Name ${event.appId.name} has id ${event.appId.id}`)")] + public void Should_replace_app_information_from_event(string script) { var @event = new EnrichedContentEvent { AppId = appId }; - var result = sut.Format("Name $APP_NAME has id $APP_ID", @event); + var result = sut.Format(script, @event); Assert.Equal($"Name my-app has id {appId.Id}", result); } - [Fact] - public void Should_replace_schema_information_from_event() + [Theory] + [InlineData("Name $SCHEMA_NAME has id $SCHEMA_ID")] + [InlineData("Script(`Name ${event.schemaId.name} has id ${event.schemaId.id}`)")] + public void Should_replace_schema_information_from_event(string script) { var @event = new EnrichedContentEvent { SchemaId = schemaId }; - var result = sut.Format("Name $SCHEMA_NAME has id $SCHEMA_ID", @event); + var result = sut.Format(script, @event); Assert.Equal($"Name my-schema has id {schemaId.Id}", result); } - [Fact] - public void Should_replace_timestamp_information_from_event() + [Theory] + [InlineData("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME")] + [InlineData("Script(`Date: ${formatDate(event.timestamp, 'yyyy-MM-dd')}, Full: ${formatDate(event.timestamp, 'yyyy-MM-dd-hh-mm-ss')}`)")] + public void Should_replace_timestamp_information_from_event(string script) { var now = DateTime.UtcNow; var envelope = new EnrichedContentEvent { Timestamp = Instant.FromDateTimeUtc(now) }; - var result = sut.Format("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); + var result = sut.Format(script, envelope); Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result); } - [Fact] - public void Should_format_email_and_display_name_from_user() + [Theory] + [InlineData("From $USER_NAME ($USER_EMAIL, $USER_ID)")] + [InlineData("Script(`From ${event.user.name} (${event.user.email}, ${event.user.id})`)")] + public void Should_format_email_and_display_name_from_user(string script) { - var @event = new EnrichedContentEvent { User = user, Actor = new RefToken(RefTokenType.Subject, "123") }; + var @event = new EnrichedContentEvent { User = user }; - var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format(script, @event); - Assert.Equal("From me (me@email.com)", result); + Assert.Equal("From me (me@email.com, 123)", result); } - [Fact] - public void Should_return_undefined_if_user_is_not_found() + [Theory] + [InlineData("From $USER_NAME ($USER_EMAIL, $USER_ID)")] + [InlineData("Script(`From ${event.user.name} (${event.user.email}, ${event.user.id})`)")] + public void Should_return_null_if_user_is_not_found(string script) { - var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Subject, "123") }; + var @event = new EnrichedContentEvent(); - var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format(script, @event); - Assert.Equal("From UNDEFINED (UNDEFINED)", result); + Assert.Equal("From null (null, null)", result); } - [Fact] - public void Should_format_email_and_display_name_from_client() + [Theory] + [InlineData("From $USER_NAME ($USER_EMAIL, $USER_ID)")] + [InlineData("Script(`From ${event.user.name} (${event.user.email}, ${event.user.id})`)")] + public void Should_format_email_and_display_name_from_client(string script) { - var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Client, "android") }; + var @event = new EnrichedContentEvent { User = new ClientUser(new RefToken(RefTokenType.Client, "android")) }; - var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format(script, @event); - Assert.Equal("From client:android (client:android)", result); + Assert.Equal("From client:android (client:android, android)", result); } - [Fact] - public void Should_replace_content_url_from_event() + [Theory] + [InlineData("Go to $CONTENT_URL")] + [InlineData("Script(`Go to ${contentUrl()}`)")] + public void Should_replace_content_url_from_event(string script) { - var url = "http://content"; + var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId }; - A.CallTo(() => urlGenerator.GenerateContentUIUrl(appId, schemaId, contentId)) - .Returns(url); + var result = sut.Format(script, @event); - var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId }; + Assert.Equal("Go to content-url", result); + } - var result = sut.Format("Go to $CONTENT_URL", @event); + [Theory] + [InlineData("Go to $CONTENT_URL")] + [InlineData("Script(`Go to ${contentUrl()}`)")] + public void Should_format_content_url_when_not_found(string script) + { + Assert.Equal("Go to null", sut.Format(script, new EnrichedAssetEvent())); + } - Assert.Equal($"Go to {url}", result); + [Theory] + [InlineData("$CONTENT_ACTION")] + [InlineData("Script(`${event.type}`)")] + public void Should_format_content_actions_when_found(string script) + { + Assert.Equal("Created", sut.Format(script, new EnrichedContentEvent { Type = EnrichedContentEventType.Created })); } - [Fact] - public void Should_format_content_url_when_not_found() + [Theory] + [InlineData("$CONTENT_ACTION")] + [InlineData("Script(contentAction())")] + public void Should_null_when_content_action_not_found(string script) { - Assert.Equal("UNDEFINED", sut.Format("$CONTENT_URL", new EnrichedAssetEvent())); + Assert.Equal("null", sut.Format(script, new EnrichedAssetEvent())); } - [Fact] - public void Should_return_undefined_when_field_not_found() + [Theory] + [InlineData("$CONTENT_DATA.country.iv")] + [InlineData("Script(`${event.data.country.iv}`)")] + public void Should_return_null_when_field_not_found(string script) { var @event = new EnrichedContentEvent { @@ -164,13 +200,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.Format("$CONTENT_DATA.country.iv", @event); + var result = sut.Format(script, @event); - Assert.Equal("UNDEFINED", result); + Assert.Equal("null", result); } - [Fact] - public void Should_return_undefined_when_partition_not_found() + [Theory] + [InlineData("$CONTENT_DATA.city.de")] + [InlineData("Script(`${event.data.country.iv}`)")] + public void Should_return_null_when_partition_not_found(string script) { var @event = new EnrichedContentEvent { @@ -181,13 +219,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.Format("$CONTENT_DATA.city.de", @event); + var result = sut.Format(script, @event); - Assert.Equal("UNDEFINED", result); + Assert.Equal("null", result); } - [Fact] - public void Should_return_undefined_when_array_item_not_found() + [Theory] + [InlineData("$CONTENT_DATA.city.iv.10")] + [InlineData("Script(`${event.data.country.de[10]}`)")] + public void Should_return_null_when_array_item_not_found(string script) { var @event = new EnrichedContentEvent { @@ -198,13 +238,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JsonValue.Array())) }; - var result = sut.Format("$CONTENT_DATA.city.de.10", @event); + var result = sut.Format(script, @event); - Assert.Equal("UNDEFINED", result); + Assert.Equal("null", result); } - [Fact] - public void Should_return_undefined_when_property_not_found() + [Theory] + [InlineData("$CONTENT_DATA.city.de.Name")] + [InlineData("Script(`${event.data.city.de.Location}`)")] + public void Should_return_null_when_property_not_found(string script) { var @event = new EnrichedContentEvent { @@ -215,13 +257,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; - var result = sut.Format("$CONTENT_DATA.city.de.Name", @event); + var result = sut.Format(script, @event); - Assert.Equal("UNDEFINED", result); + Assert.Equal("null", result); } - [Fact] - public void Should_return_plain_value_when_found() + [Theory] + [InlineData("$CONTENT_DATA.city.iv")] + [InlineData("Script(`${event.data.city.iv}`)")] + public void Should_return_plain_value_when_found(string script) { var @event = new EnrichedContentEvent { @@ -232,13 +276,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = sut.Format("$CONTENT_DATA.city.iv", @event); + var result = sut.Format(script, @event); Assert.Equal("Berlin", result); } - [Fact] - public void Should_return_plain_value_when_found_from_update_event() + [Theory] + [InlineData("$CONTENT_DATA.city.iv.0")] + [InlineData("Script(`${event.data.city.iv[0]}`)")] + public void Should_return_plain_value_from_array_when_found(string script) { var @event = new EnrichedContentEvent { @@ -246,33 +292,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", "Berlin")) + .AddValue("iv", JsonValue.Array( + "Berlin"))) }; - var result = sut.Format("$CONTENT_DATA.city.iv", @event); + var result = sut.Format(script, @event); Assert.Equal("Berlin", result); } - [Fact] - public void Should_return_undefined_when_null() - { - var @event = new EnrichedContentEvent - { - Data = - new NamedContentData() - .AddField("city", - new ContentFieldData() - .AddValue("iv", JsonValue.Null)) - }; - - var result = sut.Format("$CONTENT_DATA.city.iv", @event); - - Assert.Equal("UNDEFINED", result); - } - - [Fact] - public void Should_return_string_when_object() + [Theory] + [InlineData("$CONTENT_DATA.city.iv.name")] + [InlineData("Script(`${event.data.city.iv.name}`)")] + public void Should_return_plain_value_from_object_when_found(string script) { var @event = new EnrichedContentEvent { @@ -283,31 +315,15 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; - var result = sut.Format("$CONTENT_DATA.city.iv", @event); - - Assert.Equal("{\"name\":\"Berlin\"}", result); - } - - [Fact] - public void Should_return_plain_value_from_array_when_found() - { - var @event = new EnrichedContentEvent - { - Data = - new NamedContentData() - .AddField("city", - new ContentFieldData() - .AddValue("iv", JsonValue.Array( - "Berlin"))) - }; - - var result = sut.Format("$CONTENT_DATA.city.iv.0", @event); + var result = sut.Format(script, @event); Assert.Equal("Berlin", result); } - [Fact] - public void Should_return_plain_value_from_object_when_found() + [Theory] + [InlineData("$CONTENT_DATA.city.iv")] + [InlineData("Script(`${JSON.stringify(event.data.city.iv)}`)")] + public void Should_return_json_string_when_object(string script) { var @event = new EnrichedContentEvent { @@ -318,21 +334,20 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; - var result = sut.Format("$CONTENT_DATA.city.iv.name", @event); + var result = sut.Format(script, @event); - Assert.Equal("Berlin", result); + Assert.Equal("{\"name\":\"Berlin\"}", result); } - [Fact] - public void Should_format_content_actions_when_found() + [Theory] + [InlineData("Script(`From ${event.actor}`)")] + public void Should_format_actor(string script) { - Assert.Equal("created", sut.Format("$CONTENT_ACTION", new EnrichedContentEvent { Type = EnrichedContentEventType.Created })); - } + var @event = new EnrichedContentEvent { Actor = new RefToken(RefTokenType.Client, "android") }; - [Fact] - public void Should_format_content_actions_when_not_found() - { - Assert.Equal("UNDEFINED", sut.Format("$CONTENT_ACTION", new EnrichedAssetEvent())); + var result = sut.Format(script, @event); + + Assert.Equal("From client:android", result); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs index 90ce03bb3..cab02a7c7 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .Returns(typeof(ValidData)); A.CallTo(() => ruleTriggerHandler.TriggerType) - .Returns(typeof(ContentChangedTrigger)); + .Returns(typeof(ContentChangedTriggerV2)); sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, TestUtils.DefaultSerializer, clock, typeNameRegistry); } @@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [Fact] public async Task Should_not_create_job_if_no_action_handler_registered() { - var ruleConfig = new Rule(new ContentChangedTrigger(), new InvalidAction()); + var ruleConfig = new Rule(new ContentChangedTriggerV2(), new InvalidAction()); var ruleEnvelope = Envelope.Create(new ContentCreated()); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); @@ -132,7 +132,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules var ruleConfig = ValidRule(); var ruleEnvelope = Envelope.Create(new ContentCreated()); - A.CallTo(() => ruleTriggerHandler.Triggers(A>.Ignored, ruleConfig.Trigger)) + A.CallTo(() => ruleTriggerHandler.Triggers(A.Ignored, ruleConfig.Trigger)) .Returns(false); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); @@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => clock.GetCurrentInstant()) .Returns(now); - A.CallTo(() => ruleTriggerHandler.Triggers(A>.Ignored, ruleConfig.Trigger)) + A.CallTo(() => ruleTriggerHandler.Triggers(A.Ignored, ruleConfig.Trigger)) .Returns(true); A.CallTo(() => ruleActionHandler.CreateJobAsync(A.Ignored, ruleConfig.Action)) @@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => clock.GetCurrentInstant()) .Returns(now); - A.CallTo(() => ruleTriggerHandler.Triggers(A>.Ignored, ruleConfig.Trigger)) + A.CallTo(() => ruleTriggerHandler.Triggers(A.Ignored, ruleConfig.Trigger)) .Returns(true); A.CallTo(() => ruleActionHandler.CreateJobAsync(A.Ignored, ruleConfig.Action)) @@ -260,7 +260,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules private static Rule ValidRule() { - return new Rule(new ContentChangedTrigger(), new ValidAction()); + return new Rule(new ContentChangedTriggerV2(), new ValidAction()); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs index f83bb3e8c..9bd3890ef 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/AssetChangedTriggerTests.cs @@ -5,56 +5,80 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; +using FakeItEasy; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Assets; -using Squidex.Domain.Apps.Events.Rules; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Domain.Apps.Core.Scripting; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers { public class AssetChangedTriggerTests { - private readonly IRuleTriggerHandler sut = new AssetChangedTriggerHandler(); + private readonly IScriptEngine scriptEngine = A.Fake(); + private readonly IRuleTriggerHandler sut; - public static IEnumerable TestData + public AssetChangedTriggerTests() { - get - { - return new[] - { - new object[] { 0, 1, 1, 1, 1, new RuleCreated() }, - new object[] { 1, 1, 0, 0, 0, new AssetCreated() }, - new object[] { 0, 0, 0, 0, 0, new AssetCreated() }, - new object[] { 1, 0, 1, 0, 0, new AssetUpdated() }, - new object[] { 0, 0, 0, 0, 0, new AssetUpdated() }, - new object[] { 1, 0, 0, 1, 0, new AssetRenamed() }, - new object[] { 0, 0, 0, 0, 0, new AssetRenamed() }, - new object[] { 1, 0, 0, 0, 1, new AssetDeleted() }, - new object[] { 0, 0, 0, 0, 0, new AssetDeleted() } - }; - } + sut = new AssetChangedTriggerHandler(scriptEngine); } - [Theory] - [MemberData(nameof(TestData))] - public void Should_return_result_depending_on_event(int expected, int sendCreate, int sendUpdate, int sendRename, int sendDelete, AppEvent @event) + [Fact] + public void Should_trigger_when_condition_is_null() { - var trigger = new AssetChangedTrigger - { - SendCreate = sendCreate == 1, - SendUpdate = sendUpdate == 1, - SendRename = sendRename == 1, - SendDelete = sendDelete == 1 - }; + var trigger = new AssetChangedTriggerV2(); - var result = sut.Triggers(new Envelope(@event), trigger); + var result = sut.Triggers(new EnrichedAssetEvent(), trigger); - Assert.Equal(expected == 1, result); + Assert.True(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_trigger_when_condition_is_empty() + { + var trigger = new AssetChangedTriggerV2 { Condition = string.Empty }; + + var result = sut.Triggers(new EnrichedAssetEvent(), trigger); + + Assert.True(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_trigger_when_condition_matchs() + { + var trigger = new AssetChangedTriggerV2 { Condition = "true" }; + + var @event = new EnrichedAssetEvent(); + + A.CallTo(() => scriptEngine.Evaluate("event", @event, trigger.Condition)) + .Returns(true); + + var result = sut.Triggers(@event, trigger); + + Assert.True(result); + } + + [Fact] + public void Should_not_trigger_when_condition_does_not_matchs() + { + var trigger = new AssetChangedTriggerV2 { Condition = "false" }; + + var @event = new EnrichedAssetEvent(); + + A.CallTo(() => scriptEngine.Evaluate("event", @event, trigger.Condition)) + .Returns(false); + + var result = sut.Triggers(@event, trigger); + + Assert.False(result); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs index f24fdf033..fd65cb6ef 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs @@ -7,17 +7,14 @@ using System; using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Contents; +using System.Collections.ObjectModel; +using FakeItEasy; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.Triggers; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Events.Rules; -using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Domain.Apps.Core.Scripting; using Squidex.Infrastructure; -using Squidex.Infrastructure.Collections; -using Squidex.Infrastructure.EventSourcing; using Xunit; #pragma warning disable SA1401 // Fields must be private @@ -26,82 +23,164 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers { public class ContentChangedTriggerTests { - private readonly IRuleTriggerHandler sut = new ContentChangedTriggerHandler(); + private readonly IScriptEngine scriptEngine = A.Fake(); + private readonly IRuleTriggerHandler sut; private static readonly NamedId SchemaMatch = NamedId.Of(Guid.NewGuid(), "my-schema1"); private static readonly NamedId SchemaNonMatch = NamedId.Of(Guid.NewGuid(), "my-schema2"); - public static IEnumerable TestData = new[] + public ContentChangedTriggerTests() { - new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new RuleCreated() }, - new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new ContentCreated { SchemaId = SchemaNonMatch } }, - new object[] { 1, 1, 0, 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentCreated { SchemaId = SchemaMatch } }, - new object[] { 1, 0, 1, 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentUpdated { SchemaId = SchemaMatch } }, - new object[] { 1, 0, 0, 1, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } }, - new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentDeleted { SchemaId = SchemaMatch } }, - new object[] { 1, 1, 1, 1, 0, 0, 1, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Archived } }, - new object[] { 0, 1, 1, 1, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Archived } }, - new object[] { 1, 0, 0, 0, 0, 0, 0, 1, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Restored } }, - new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Restored } }, - new object[] { 1, 0, 0, 0, 1, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Published } }, - new object[] { 0, 0, 0, 0, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Published } }, - new object[] { 1, 1, 1, 1, 0, 1, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Unpublished } }, - new object[] { 0, 1, 1, 1, 0, 0, 0, 0, new ContentStatusChanged { SchemaId = SchemaMatch, Change = StatusChange.Unpublished } }, - new object[] { 0, 1, 1, 1, 1, 0, 0, 0, new SchemaCreated { SchemaId = SchemaNonMatch } } - }; + sut = new ContentChangedTriggerHandler(scriptEngine); + } [Fact] - public void Should_return_false_when_trigger_contains_no_schemas() + public void Should_not_trigger_when_o_contains_no_schemas() { - var trigger = new ContentChangedTrigger(); + var trigger = new ContentChangedTriggerV2(); + + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; - var result = sut.Triggers(new Envelope(new ContentCreated()), trigger); + var result = sut.Triggers(@event, trigger); Assert.False(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); } [Fact] - public void Should_return_true_when_cathing_all_events() + public void Should_trigger_when_cathing_all_events() { - var trigger = new ContentChangedTrigger { HandleAll = true }; + var trigger = new ContentChangedTriggerV2 { HandleAll = true }; + + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; - var result = sut.Triggers(new Envelope(new ContentCreated()), trigger); + var result = sut.Triggers(@event, trigger); Assert.True(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); } - [Theory] - [MemberData(nameof(TestData))] - public void Should_return_result_depending_on_event(int expected, - int sendCreate, - int sendUpdate, - int sendDelete, - int sendPublish, - int sendUnpublish, - int sendArchive, - int sendRestore, - AppEvent @event) + [Fact] + public void Should_trigger_when_condition_is_null() { - var trigger = new ContentChangedTrigger + var trigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Create( - new ContentChangedTriggerSchema + Schemas = new ReadOnlyCollection(new List + { + new ContentChangedTriggerSchemaV2 { - SendCreate = sendCreate == 1, - SendUpdate = sendUpdate == 1, - SendDelete = sendDelete == 1, - SendPublish = sendPublish == 1, - SendUnpublish = sendUnpublish == 1, - SendArchived = sendArchive == 1, - SendRestore = sendRestore == 1, SchemaId = SchemaMatch.Id - }) + } + }) }; - var result = sut.Triggers(new Envelope(@event), trigger); + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; + + var result = sut.Triggers(@event, trigger); + + Assert.True(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_not_trigger_when_schema_id_does_not_match() + { + var trigger = new ContentChangedTriggerV2 + { + Schemas = new ReadOnlyCollection(new List + { + new ContentChangedTriggerSchemaV2 + { + SchemaId = SchemaNonMatch.Id + } + }) + }; + + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; + + var result = sut.Triggers(@event, trigger); + + Assert.False(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_trigger_when_condition_is_empty() + { + var trigger = new ContentChangedTriggerV2 + { + Schemas = new ReadOnlyCollection(new List + { + new ContentChangedTriggerSchemaV2 + { + SchemaId = SchemaMatch.Id, Condition = string.Empty + } + }) + }; - Assert.Equal(expected == 1, result); + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; + + var result = sut.Triggers(@event, trigger); + + Assert.True(result); + + A.CallTo(() => scriptEngine.Evaluate(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public void Should_trigger_when_condition_matchs() + { + var trigger = new ContentChangedTriggerV2 + { + Schemas = new ReadOnlyCollection(new List + { + new ContentChangedTriggerSchemaV2 + { + SchemaId = SchemaMatch.Id, Condition = "true" + } + }) + }; + + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; + + A.CallTo(() => scriptEngine.Evaluate("event", @event, "true")) + .Returns(true); + + var result = sut.Triggers(@event, trigger); + + Assert.True(result); + } + + [Fact] + public void Should_not_trigger_when_condition_does_not_matchs() + { + var trigger = new ContentChangedTriggerV2 + { + Schemas = new ReadOnlyCollection(new List + { + new ContentChangedTriggerSchemaV2 + { + SchemaId = SchemaMatch.Id, Condition = "false" + } + }) + }; + + var @event = new EnrichedContentEvent { SchemaId = SchemaMatch }; + + A.CallTo(() => scriptEngine.Evaluate("event", @event, "false")) + .Returns(false); + + var result = sut.Triggers(@event, trigger); + + Assert.False(result); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs index cc32eb797..e91b0dc96 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var engine = new Engine(); - engine.SetValue("user", new JintUser(engine, new ClaimsPrincipal(new[] { identity }))); + engine.SetValue("user", JintUser.Create(engine, new ClaimsPrincipal(new[] { identity }))); return engine.Execute(script).GetCompletionValue().ToObject(); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj index c68aff69b..ed4d7b441 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 837387d2a..50c196f5a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards public class GuardRuleTests { private readonly Uri validUrl = new Uri("https://squidex.io"); - private readonly Rule rule_0 = new Rule(new ContentChangedTrigger(), new TestAction()); + private readonly Rule rule_0 = new Rule(new ContentChangedTriggerV2(), new TestAction()); private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly IAppProvider appProvider = A.Fake(); @@ -60,9 +60,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = CreateCommand(new CreateRule { - Trigger = new ContentChangedTrigger + Trigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }, Action = null }); @@ -76,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = CreateCommand(new CreateRule { - Trigger = new ContentChangedTrigger + Trigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }, Action = new TestAction { @@ -103,9 +103,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { var command = new UpdateRule { - Trigger = new ContentChangedTrigger + Trigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }, Action = new TestAction { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs index 4e1f6af25..5d386c6ac 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -26,10 +26,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(Task.FromResult(null)); - var trigger = new ContentChangedTrigger + var trigger = new ContentChangedTriggerV2 { Schemas = ReadOnlyCollection.Create( - new ContentChangedTriggerSchema() + new ContentChangedTriggerSchemaV2() ) }; @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers [Fact] public async Task Should_not_add_error_if_schemas_is_null() { - var trigger = new ContentChangedTrigger(); + var trigger = new ContentChangedTriggerV2(); var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); @@ -51,9 +51,9 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers [Fact] public async Task Should_not_add_error_if_schemas_is_empty() { - var trigger = new ContentChangedTrigger + var trigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }; var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); @@ -67,10 +67,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(A.Fake()); - var trigger = new ContentChangedTrigger + var trigger = new ContentChangedTriggerV2 { Schemas = ReadOnlyCollection.Create( - new ContentChangedTriggerSchema() + new ContentChangedTriggerSchemaV2() ) }; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index b2396de0e..0822d1b77 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -70,9 +70,9 @@ namespace Squidex.Domain.Apps.Entities.Rules { var @event = Envelope.Create(new ContentCreated { AppId = appId }); - var rule1 = new Rule(new ContentChangedTrigger(), new TestAction { Url = new Uri("https://squidex.io") }); - var rule2 = new Rule(new ContentChangedTrigger(), new TestAction { Url = new Uri("https://squidex.io") }); - var rule3 = new Rule(new ContentChangedTrigger(), new TestAction { Url = new Uri("https://squidex.io") }); + var rule1 = new Rule(new ContentChangedTriggerV2(), new TestAction { Url = new Uri("https://squidex.io") }); + var rule2 = new Rule(new ContentChangedTriggerV2(), new TestAction { Url = new Uri("https://squidex.io") }); + var rule3 = new Rule(new ContentChangedTriggerV2(), new TestAction { Url = new Uri("https://squidex.io") }); var job1 = new RuleJob { Created = now }; var job2 = new RuleJob { Created = now }; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs index eae59aba9..182741bcb 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs @@ -189,9 +189,9 @@ namespace Squidex.Domain.Apps.Entities.Rules private static CreateRule MakeCreateCommand() { - var newTrigger = new ContentChangedTrigger + var newTrigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }; var newAction = new TestAction @@ -204,9 +204,9 @@ namespace Squidex.Domain.Apps.Entities.Rules private static UpdateRule MakeUpdateCommand() { - var newTrigger = new ContentChangedTrigger + var newTrigger = new ContentChangedTriggerV2 { - Schemas = ReadOnlyCollection.Empty() + Schemas = ReadOnlyCollection.Empty() }; var newAction = new TestAction diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index c3cd39700..20340d722 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj index 6a0f7b16e..ca2daac0e 100644 --- a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj +++ b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs index 60e2b769f..74529224f 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs @@ -15,7 +15,7 @@ namespace Squidex.Infrastructure.EventSourcing { public class DefaultEventDataFormatterTests { - public sealed class MyOldEvent : IEvent, IMigratedEvent + public sealed class MyOldEvent : IEvent, IMigrated { public string MyProperty { get; set; } diff --git a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj index 576a53a5c..02579d476 100644 --- a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj +++ b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Squidex.Tests/Squidex.Tests.csproj b/tests/Squidex.Tests/Squidex.Tests.csproj index d1c85aa9d..43a04e1fe 100644 --- a/tests/Squidex.Tests/Squidex.Tests.csproj +++ b/tests/Squidex.Tests/Squidex.Tests.csproj @@ -11,11 +11,11 @@ - + - + diff --git a/tools/Migrate_01/OldEvents/AppClientChanged.cs b/tools/Migrate_01/OldEvents/AppClientChanged.cs index d2caf075a..e656bf5af 100644 --- a/tools/Migrate_01/OldEvents/AppClientChanged.cs +++ b/tools/Migrate_01/OldEvents/AppClientChanged.cs @@ -7,6 +7,7 @@ using System; using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -14,7 +15,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(AppClientChanged))] [Obsolete] - public sealed class AppClientChanged : AppEvent, IMigratedEvent + public sealed class AppClientChanged : AppEvent, IMigrated { public string Id { get; set; } diff --git a/tools/Migrate_01/OldEvents/AppClientUpdated.cs b/tools/Migrate_01/OldEvents/AppClientUpdated.cs index 41786be33..5558bde7b 100644 --- a/tools/Migrate_01/OldEvents/AppClientUpdated.cs +++ b/tools/Migrate_01/OldEvents/AppClientUpdated.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; using AppClientUpdatedV2 = Squidex.Domain.Apps.Events.Apps.AppClientUpdated; @@ -16,7 +17,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(AppClientUpdated))] [Obsolete] - public sealed class AppClientUpdated : AppEvent, IMigratedEvent + public sealed class AppClientUpdated : AppEvent, IMigrated { public string Id { get; set; } diff --git a/tools/Migrate_01/OldEvents/AppContributorAssigned.cs b/tools/Migrate_01/OldEvents/AppContributorAssigned.cs index fbf25e427..ffb543372 100644 --- a/tools/Migrate_01/OldEvents/AppContributorAssigned.cs +++ b/tools/Migrate_01/OldEvents/AppContributorAssigned.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; using AppContributorAssignedV2 = Squidex.Domain.Apps.Events.Apps.AppContributorAssigned; @@ -16,7 +17,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(AppContributorAssigned))] [Obsolete] - public sealed class AppContributorAssigned : AppEvent, IMigratedEvent + public sealed class AppContributorAssigned : AppEvent, IMigrated { public string ContributorId { get; set; } diff --git a/tools/Migrate_01/OldEvents/ContentArchived.cs b/tools/Migrate_01/OldEvents/ContentArchived.cs index 1f6397d22..5933328a3 100644 --- a/tools/Migrate_01/OldEvents/ContentArchived.cs +++ b/tools/Migrate_01/OldEvents/ContentArchived.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -15,7 +16,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(ContentArchived))] [Obsolete] - public sealed class ContentArchived : ContentEvent, IMigratedEvent + public sealed class ContentArchived : ContentEvent, IMigrated { public IEvent Migrate() { diff --git a/tools/Migrate_01/OldEvents/ContentPublished.cs b/tools/Migrate_01/OldEvents/ContentPublished.cs index 247141281..82a2b6099 100644 --- a/tools/Migrate_01/OldEvents/ContentPublished.cs +++ b/tools/Migrate_01/OldEvents/ContentPublished.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -15,7 +16,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(ContentPublished))] [Obsolete] - public sealed class ContentPublished : ContentEvent, IMigratedEvent + public sealed class ContentPublished : ContentEvent, IMigrated { public IEvent Migrate() { diff --git a/tools/Migrate_01/OldEvents/ContentRestored.cs b/tools/Migrate_01/OldEvents/ContentRestored.cs index 8df1ce037..844ecb65e 100644 --- a/tools/Migrate_01/OldEvents/ContentRestored.cs +++ b/tools/Migrate_01/OldEvents/ContentRestored.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -15,7 +16,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(ContentRestored))] [Obsolete] - public sealed class ContentRestored : ContentEvent, IMigratedEvent + public sealed class ContentRestored : ContentEvent, IMigrated { public IEvent Migrate() { diff --git a/tools/Migrate_01/OldEvents/ContentUnpublished.cs b/tools/Migrate_01/OldEvents/ContentUnpublished.cs index 993f7019d..5995d7e84 100644 --- a/tools/Migrate_01/OldEvents/ContentUnpublished.cs +++ b/tools/Migrate_01/OldEvents/ContentUnpublished.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; @@ -15,7 +16,7 @@ namespace Migrate_01.OldEvents { [EventType(nameof(ContentUnpublished))] [Obsolete] - public sealed class ContentUnpublished : ContentEvent, IMigratedEvent + public sealed class ContentUnpublished : ContentEvent, IMigrated { public IEvent Migrate() { diff --git a/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs b/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs new file mode 100644 index 000000000..ad124dac1 --- /dev/null +++ b/tools/Migrate_01/OldTriggers/AssetChangedTrigger.cs @@ -0,0 +1,71 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure; + +namespace Migrate_01.OldTriggers +{ + [TypeName(nameof(AssetChangedTrigger))] + public sealed class AssetChangedTrigger : RuleTrigger, IMigrated + { + public bool SendCreate { get; set; } + + public bool SendUpdate { get; set; } + + public bool SendRename { get; set; } + + public bool SendDelete { get; set; } + + public override T Accept(IRuleTriggerVisitor visitor) + { + throw new NotSupportedException(); + } + + public RuleTrigger Migrate() + { + var conditions = new List(); + + if (SendCreate) + { + conditions.Add($"event.type == '{EnrichedAssetEventType.Created}'"); + } + + if (SendUpdate) + { + conditions.Add($"event.type == '{EnrichedAssetEventType.Updated}'"); + } + + if (SendRename) + { + conditions.Add($"event.type == '{EnrichedAssetEventType.Renamed}'"); + } + + if (SendDelete) + { + conditions.Add($"event.type == '{EnrichedAssetEventType.Deleted}'"); + } + + var condition = string.Empty; + + if (conditions.Count == 0) + { + condition = "false"; + } + else if (condition.Length < 4) + { + condition = string.Join(" || ", conditions); + } + + return new AssetChangedTriggerV2 { Condition = condition }; + } + } +} diff --git a/tools/Migrate_01/OldTriggers/ContentChangedTrigger.cs b/tools/Migrate_01/OldTriggers/ContentChangedTrigger.cs new file mode 100644 index 000000000..b7890f1cf --- /dev/null +++ b/tools/Migrate_01/OldTriggers/ContentChangedTrigger.cs @@ -0,0 +1,49 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.ObjectModel; +using System.Linq; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure; + +namespace Migrate_01.OldTriggers +{ + [TypeName(nameof(ContentChangedTrigger))] + public sealed class ContentChangedTrigger : RuleTrigger, IMigrated + { + public ReadOnlyCollection Schemas { get; set; } + + public bool HandleAll { get; set; } + + public override T Accept(IRuleTriggerVisitor visitor) + { + throw new NotSupportedException(); + } + + public override void Freeze() + { + base.Freeze(); + + if (Schemas != null) + { + foreach (var schema in Schemas) + { + schema.Freeze(); + } + } + } + + public RuleTrigger Migrate() + { + var schemas = new ReadOnlyCollection(Schemas.Select(x => x.Migrate()).ToList()); + + return new ContentChangedTriggerV2 { HandleAll = HandleAll, Schemas = schemas }; + } + } +} diff --git a/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs b/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs new file mode 100644 index 000000000..651df9456 --- /dev/null +++ b/tools/Migrate_01/OldTriggers/ContentChangedTriggerSchema.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Core.Rules.Triggers; + +namespace Migrate_01.OldTriggers +{ + public sealed class ContentChangedTriggerSchema : Freezable + { + 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; } + + public bool SendUnpublish { get; set; } + + public bool SendArchived { get; set; } + + public bool SendRestore { get; set; } + + public ContentChangedTriggerSchemaV2 Migrate() + { + var conditions = new List(); + + if (SendCreate) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Created}'"); + } + + if (SendUpdate) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Updated}'"); + } + + if (SendPublish) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Published}'"); + } + + if (SendUnpublish) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Unpublished}'"); + } + + if (SendArchived) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Archived}'"); + } + + if (SendRestore) + { + conditions.Add($"event.type == '{EnrichedContentEventType.Restored}'"); + } + + if (SendDelete) + { + conditions.Add($"event.type == '{EnrichedAssetEventType.Deleted}'"); + } + + var condition = string.Empty; + + if (conditions.Count == 0 && condition.Length < 7) + { + condition = "false"; + } + else if (condition.Length < 7) + { + condition = string.Join(" || ", conditions); + } + + return new ContentChangedTriggerSchemaV2 { SchemaId = SchemaId, Condition = condition }; + } + } +}