diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs index 3d7171141..12d0bf225 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions }); } - protected override async Task<(string Description, AlgoliaJob Data)> CreateJobAsync(EnrichedEvent @event, AlgoliaAction action) + protected override (string Description, AlgoliaJob Data) CreateJob(EnrichedEvent @event, AlgoliaAction action) { if (@event is EnrichedContentEvent contentEvent) { @@ -64,11 +64,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions AppId = action.AppId, ApiKey = action.ApiKey, ContentId = contentId, - IndexName = await formatter.FormatStringAsync(action.IndexName, @event) + IndexName = formatter.Format(action.IndexName, @event) }; - if (contentEvent.Action == EnrichedContentEventAction.Deleted || - contentEvent.Action == EnrichedContentEventAction.Unpublished) + if (contentEvent.Type == EnrichedContentEventType.Deleted || + contentEvent.Type == EnrichedContentEventType.Unpublished) { ruleDescription = $"Delete entry from Algolia index: {action.IndexName}"; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs index fece94b2f..72af2b839 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs @@ -60,11 +60,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions }); } - protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(EnrichedEvent @event, AzureQueueAction action) + protected override (string Description, AzureQueueJob Data) CreateJob(EnrichedEvent @event, AzureQueueAction action) { var body = formatter.ToEnvelope(@event).ToString(Formatting.Indented); - var queueName = await formatter.FormatStringAsync(action.Queue, @event); + var queueName = formatter.Format(action.Queue, @event); var ruleDescription = $"Send AzureQueueJob to azure queue '{action.Queue}'"; var ruleJob = new AzureQueueJob diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs index 624795c6b..b51b2d5bf 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions }); } - protected override async Task<(string Description, ElasticSearchJob Data)> CreateJobAsync(EnrichedEvent @event, ElasticSearchAction action) + protected override (string Description, ElasticSearchJob Data) CreateJob(EnrichedEvent @event, ElasticSearchAction action) { if (@event is EnrichedContentEvent contentEvent) { @@ -73,12 +73,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions Username = action.Username, Password = action.Password, ContentId = contentId, - IndexName = await formatter.FormatStringAsync(action.IndexName, @event), - IndexType = await formatter.FormatStringAsync(action.IndexType, @event), + IndexName = formatter.Format(action.IndexName, @event), + IndexType = formatter.Format(action.IndexType, @event), }; - if (contentEvent.Action == EnrichedContentEventAction.Deleted || - contentEvent.Action == EnrichedContentEventAction.Unpublished) + if (contentEvent.Type == EnrichedContentEventType.Deleted || + contentEvent.Type == EnrichedContentEventType.Unpublished) { ruleDescription = $"Delete entry index: {action.IndexName}"; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs index ef4f8c0e8..937674741 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions { private const string Description = "Purge key in fastly"; - protected override Task<(string Description, FastlyJob Data)> CreateJobAsync(EnrichedEvent @event, FastlyAction action) + protected override (string Description, FastlyJob Data) CreateJob(EnrichedEvent @event, FastlyAction action) { var ruleJob = new FastlyJob { @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions FastlyServiceID = action.ServiceId }; - return Task.FromResult((Description, ruleJob)); + return (Description, ruleJob); } protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job) diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs index 62863eee5..ba089042f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions this.formatter = formatter; } - protected override async Task<(string Description, MediumJob Data)> CreateJobAsync(EnrichedEvent @event, MediumAction action) + protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) { var requestUrl = !string.IsNullOrWhiteSpace(action.Author) ? @@ -51,11 +51,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions var requestBody = new JObject( - new JProperty("title", await formatter.FormatStringAsync(action.Title, @event)), + new JProperty("title", formatter.Format(action.Title, @event)), new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"), - new JProperty("content", await formatter.FormatStringAsync(action.Content, @event)), - new JProperty("canonicalUrl", await formatter.FormatStringAsync(action.CanonicalUrl, @event)), - new JProperty("tags", await ParseTagsAsync(@event, action))); + new JProperty("content", formatter.Format(action.Content, @event)), + new JProperty("canonicalUrl", formatter.Format(action.CanonicalUrl, @event)), + new JProperty("tags", ParseTags(@event, action))); var ruleJob = new MediumJob { @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions return (Description, ruleJob); } - private async Task ParseTagsAsync(EnrichedEvent @event, MediumAction action) + private JArray ParseTags(EnrichedEvent @event, MediumAction action) { if (string.IsNullOrWhiteSpace(action.Tags)) { @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions string[] tags; try { - var jsonTags = await formatter.FormatStringAsync(action.Tags, @event); + var jsonTags = formatter.Format(action.Tags, @event); tags = JsonConvert.DeserializeObject(jsonTags); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs index c783f8ee7..c7f64fbe4 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs @@ -49,11 +49,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions this.formatter = formatter; } - protected override async Task<(string Description, SlackJob Data)> CreateJobAsync(EnrichedEvent @event, SlackAction action) + protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) { var body = new JObject( - new JProperty("text", await formatter.FormatStringAsync(action.Text, @event))); + new JProperty("text", formatter.Format(action.Text, @event))); var ruleJob = new SlackJob { diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs index d7a995810..385d47530 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs @@ -50,10 +50,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions this.formatter = formatter; } - protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(EnrichedEvent @event, WebhookAction action) + protected override (string Description, WebhookJob Data) CreateJob(EnrichedEvent @event, WebhookAction action) { var requestBody = formatter.ToEnvelope(@event).ToString(Formatting.Indented); - var requestUrl = await formatter.FormatStringAsync(action.Url.ToString(), @event); + var requestUrl = formatter.Format(action.Url.ToString(), @event); var ruleDescription = $"Send event to webhook '{requestUrl}'"; var ruleJob = new WebhookJob diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs new file mode 100644 index 000000000..56e0303b0 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs @@ -0,0 +1,47 @@ +// ========================================================================== +// 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 sealed class EnrichedAssetEvent : EnrichedEvent + { + 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; } + + public long FileVersion { get; set; } + + public long FileSize { get; set; } + + public bool IsImage { get; set; } + + 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/EnrichedAssetEventType.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs new file mode 100644 index 000000000..0e66499b2 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents +{ + public enum EnrichedAssetEventType + { + Created, + Deleted, + Renamed, + Updated + } +} \ No newline at end of file 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 730845f3b..88af9945c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { public sealed class EnrichedContentEvent : EnrichedSchemaEvent { - public EnrichedContentEventAction Action { get; set; } + public EnrichedContentEventType Type { get; set; } public Guid Id { get; set; } @@ -29,5 +29,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents 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/EnrichedContentEventAction.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs similarity index 92% rename from src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventAction.cs rename to src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs index 6c02c8f48..ccefbeaf2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventAction.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { - public enum EnrichedContentEventAction + public enum EnrichedContentEventType { Created, Deleted, 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 d169ccdc9..b95a939bf 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs @@ -6,21 +6,30 @@ // ========================================================================== using System; +using Newtonsoft.Json; using NodaTime; using Squidex.Infrastructure; +using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents { - public class EnrichedEvent + public abstract class EnrichedEvent { - public Guid AggregateId { get; set; } - public NamedId AppId { get; set; } public RefToken Actor { get; set; } public Instant Timestamp { get; set; } + public long Version { get; set; } + + [JsonIgnore] + public abstract Guid AggregateId { get; } + + [JsonIgnore] public string Name { get; set; } + + [JsonIgnore] + public IUser User { get; set; } } } 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 d62e2493a..528121c3b 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 class EnrichedSchemaEvent : EnrichedEvent + public abstract class EnrichedSchemaEvent : EnrichedEvent { public NamedId SchemaId { get; set; } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs index 1cf0e8666..ddab38183 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs @@ -11,6 +11,8 @@ using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; +#pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar + namespace Squidex.Domain.Apps.Core.HandleRules { public abstract class RuleActionHandler : IRuleActionHandler where TAction : RuleAction @@ -34,7 +36,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules return await ExecuteJobAsync(typedData); } - protected abstract Task<(string Description, TData Data)> CreateJobAsync(EnrichedEvent @event, TAction action); + protected virtual Task<(string Description, TData Data)> CreateJobAsync(EnrichedEvent @event, TAction action) + { + return Task.FromResult(CreateJob(@event, action)); + } + + protected virtual (string Description, TData Data) CreateJob(EnrichedEvent @event, TAction action) + { + throw new NotImplementedException(); + } protected abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(TData job); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index 5910dd143..c724565b8 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -6,17 +6,15 @@ // =========================================-================================= using System; +using System.Collections.Generic; using System.Globalization; using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Core.HandleRules @@ -24,42 +22,38 @@ namespace Squidex.Domain.Apps.Core.HandleRules public class RuleEventFormatter { private const string Undefined = "UNDEFINED"; - private const string AppIdPlaceholder = "$APP_ID"; - private const string AppNamePlaceholder = "$APP_NAME"; - private const string SchemaIdPlaceholder = "$SCHEMA_ID"; - private const string SchemaNamePlaceholder = "$SCHEMA_NAME"; - private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE"; - private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME"; - private const string ContentActionPlaceholder = "$CONTENT_ACTION"; - private const string ContentUrlPlaceholder = "$CONTENT_URL"; - private const string UserNamePlaceholder = "$USER_NAME"; - private const string UserEmailPlaceholder = "$USER_EMAIL"; - private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); - private static readonly Regex ContentDataPlaceholderV2 = new Regex(@"\$\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled); - private static readonly TimeSpan UserCacheDuration = TimeSpan.FromMinutes(10); + private static readonly Regex ContentDataPlaceholder = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); + private static readonly Regex ContentDataPlaceholder2 = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled); + private readonly List<(string Pattern, Func Replacer)> patterns = new List<(string Pattern, Func Replacer)>(); private readonly JsonSerializer serializer; private readonly IRuleUrlGenerator urlGenerator; - private readonly IMemoryCache memoryCache; - private readonly IUserResolver userResolver; - public RuleEventFormatter( - JsonSerializer serializer, - IRuleUrlGenerator urlGenerator, - IMemoryCache memoryCache, - IUserResolver userResolver) + public RuleEventFormatter(JsonSerializer serializer, IRuleUrlGenerator urlGenerator) { - Guard.NotNull(memoryCache, nameof(memoryCache)); Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(urlGenerator, nameof(urlGenerator)); - Guard.NotNull(userResolver, nameof(userResolver)); - this.memoryCache = memoryCache; this.serializer = serializer; - this.userResolver = userResolver; this.urlGenerator = urlGenerator; + + AddPattern("APP_ID", AppId); + AddPattern("APP_NAME", AppName); + AddPattern("CONTENT_ACTION", ContentAction); + AddPattern("CONTENT_URL", ContentUrl); + AddPattern("SCHEMA_ID", SchemaId); + AddPattern("SCHEMA_NAME", SchemaName); + AddPattern("TIMESTAMP_DATETIME", TimestampTime); + AddPattern("TIMESTAMP_DATE", TimestampDate); + AddPattern("USER_NAME", UserName); + AddPattern("USER_EMAIL", UserEmail); } - public virtual JObject ToPayload(object @event) + private void AddPattern(string placeholder, Func generator) + { + patterns.Add((placeholder, generator)); + } + + public virtual JObject ToPayload(T @event) { return JObject.FromObject(@event, serializer); } @@ -67,102 +61,184 @@ namespace Squidex.Domain.Apps.Core.HandleRules public virtual JObject ToEnvelope(EnrichedEvent @event) { return new JObject( - new JProperty("type", @event), + new JProperty("type", @event.Name), new JProperty("payload", ToPayload(@event)), new JProperty("timestamp", @event.Timestamp.ToString())); } - public async virtual Task FormatStringAsync(string text, EnrichedEvent @event) + public string Format(string text, EnrichedEvent @event) { if (string.IsNullOrWhiteSpace(text)) { return text; } - var sb = new StringBuilder(text); + var current = text.AsSpan(); - sb.Replace(TimestampDateTimePlaceholder, @event.Timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture)); - sb.Replace(TimestampDatePlaceholder, @event.Timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture)); + var sb = new StringBuilder(); - if (@event.AppId != null) + for (var i = 0; i < current.Length; i++) { - sb.Replace(AppIdPlaceholder, @event.AppId.Id.ToString()); - sb.Replace(AppNamePlaceholder, @event.AppId.Name); + var c = current[i]; + + if (c == '$') + { + sb.Append(current.Slice(0, i)); + + current = current.Slice(i); + + var test = current.Slice(1); + var tested = false; + + for (var j = 0; j < patterns.Count; j++) + { + var (Pattern, Replacer) = patterns[j]; + + if (test.StartsWith(Pattern, StringComparison.OrdinalIgnoreCase)) + { + sb.Append(Replacer(@event)); + + current = current.Slice(Pattern.Length + 1); + i = 0; + + tested = true; + break; + } + } + + if (!tested && + (test.StartsWith("CONTENT_DATA", StringComparison.OrdinalIgnoreCase) || + test.StartsWith("{CONTENT_DATA", StringComparison.OrdinalIgnoreCase))) + { + var currentString = new string(test); + + var match = ContentDataPlaceholder.Match(currentString); + + if (!match.Success) + { + match = ContentDataPlaceholder2.Match(currentString); + } + + if (match.Success) + { + if (@event is EnrichedContentEvent contentEvent) + { + sb.Append(CalculateData(contentEvent.Data, match)); + } + else + { + sb.Append(Undefined); + } + + current = current.Slice(match.Length + 1); + i = 0; + } + } + } } - if (@event is EnrichedSchemaEvent schemaEvent && schemaEvent.SchemaId != null) + sb.Append(current); + + return sb.ToString(); + } + + private static string TimestampDate(EnrichedEvent @event) + { + return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd", CultureInfo.InvariantCulture); + } + + private static string TimestampTime(EnrichedEvent @event) + { + return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture); + } + + private static string AppId(EnrichedEvent @event) + { + return @event.AppId.Id.ToString(); + } + + private static string AppName(EnrichedEvent @event) + { + return @event.AppId.Name; + } + + private static string SchemaId(EnrichedEvent @event) + { + if (@event is EnrichedSchemaEvent schemaEvent) { - sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString()); - sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name); + return schemaEvent.SchemaId.Id.ToString(); } - if (@event is EnrichedContentEvent contentEvent) + return Undefined; + } + + private static string SchemaName(EnrichedEvent @event) + { + if (@event is EnrichedSchemaEvent schemaEvent) { - sb.Replace(ContentUrlPlaceholder, urlGenerator.GenerateContentUIUrl(@event.AppId, contentEvent.SchemaId, contentEvent.Id)); - sb.Replace(ContentActionPlaceholder, contentEvent.Action.ToString()); + return schemaEvent.SchemaId.Name; } - await FormatUserInfoAsync(@event, sb); - - var result = sb.ToString(); + return Undefined; + } - if (@event is EnrichedContentEvent contentEvent2) + private static string ContentAction(EnrichedEvent @event) + { + if (@event is EnrichedContentEvent contentEvent) { - result = ReplaceData(contentEvent2.Data, result); + return contentEvent.Type.ToString().ToLowerInvariant(); } - return result; + return Undefined; } - private async Task FormatUserInfoAsync(EnrichedEvent @event, StringBuilder sb) + private string ContentUrl(EnrichedEvent @event) { - var text = sb.ToString(); - - if (text.Contains(UserEmailPlaceholder) || text.Contains(UserNamePlaceholder)) + if (@event is EnrichedContentEvent contentEvent) { - var actor = @event.Actor; + return urlGenerator.GenerateContentUIUrl(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id); + } - if (actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase)) - { - var displayText = actor.ToString(); + return Undefined; + } - sb.Replace(UserEmailPlaceholder, displayText); - sb.Replace(UserNamePlaceholder, displayText); - } - else + private static string UserName(EnrichedEvent @event) + { + if (@event.Actor != null) + { + if (@event.Actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase)) { - var user = await FindUserAsync(actor); + return @event.Actor.ToString(); + } - if (user != null) - { - sb.Replace(UserEmailPlaceholder, user.Email); - sb.Replace(UserNamePlaceholder, user.DisplayName()); - } - else - { - sb.Replace(UserEmailPlaceholder, Undefined); - sb.Replace(UserNamePlaceholder, Undefined); - } + if (@event.User != null) + { + return @event.User.DisplayName(); } } + + return Undefined; } - private static string ReplaceData(NamedContentData data, string text) + private static string UserEmail(EnrichedEvent @event) { - text = ContentDataPlaceholder.Replace(text, match => + if (@event.Actor != null) { - return Replace(data, match); - }); + if (@event.Actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase)) + { + return @event.Actor.ToString(); + } - text = ContentDataPlaceholderV2.Replace(text, match => - { - return Replace(data, match); - }); + if (@event.User != null) + { + return @event.User.Email; + } + } - return text; + return Undefined; } - private static string Replace(NamedContentData data, Match match) + private static string CalculateData(NamedContentData data, Match match) { var captures = match.Groups[2].Captures; @@ -212,24 +288,5 @@ namespace Squidex.Domain.Apps.Core.HandleRules return value?.ToString(Formatting.Indented) ?? Undefined; } - - private Task FindUserAsync(RefToken actor) - { - var key = $"RuleEventFormatter_Users_${actor.Identifier}"; - - return memoryCache.GetOrCreateAsync(key, async x => - { - x.AbsoluteExpirationRelativeToNow = UserCacheDuration; - - try - { - return await userResolver.FindByIdOrEmailAsync(actor.Identifier); - } - catch - { - return null; - } - }); - } } } 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 a109f585f..2bd9be1b5 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 @@ -1,6 +1,6 @@  - netstandard2.0 + netcoreapp2.1 Squidex.Domain.Apps.Core 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 0e0ab5bc1..7cb1a8a49 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 @@ -1,6 +1,6 @@  - netstandard2.0 + netcoreapp2.1 full diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs index 43bf53061..6839d4b56 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -26,7 +26,7 @@ using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Apps { - public class AppGrain : SquidexDomainObjectGrain, IAppGrain + public sealed class AppGrain : SquidexDomainObjectGrain, IAppGrain { private readonly InitialPatterns initialPatterns; private readonly IAppProvider appProvider; diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs index d9d1520b4..00fa7b95e 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs @@ -22,7 +22,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Assets { - public class AssetGrain : SquidexDomainObjectGrain, IAssetGrain + public sealed class AssetGrain : SquidexDomainObjectGrain, IAssetGrain { public AssetGrain(IStore store, ISemanticLog log) : base(store, log) @@ -140,9 +140,9 @@ namespace Squidex.Domain.Apps.Entities.Assets ApplySnapshot(Snapshot.Apply(@event)); } - public Task> GetStateAsync() + public Task> GetStateAsync(long version = EtagVersion.Empty) { - return J.AsTask(Snapshot); + return J.AsTask(GetSnapshot(version)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs index 864c0256a..4018d7e3d 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs @@ -5,11 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Threading.Tasks; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetGrain : IDomainObjectGrain { + Task> GetStateAsync(long version = EtagVersion.Any); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 161f433ae..e9439de49 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -26,7 +26,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Contents { - public class ContentGrain : SquidexDomainObjectGrain, IContentGrain + public sealed class ContentGrain : SquidexDomainObjectGrain, IContentGrain { private readonly IAppProvider appProvider; private readonly IAssetRepository assetRepository; @@ -293,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public Task> GetStateAsync(long version = -2) { - return Task.FromResult(J.Of(GetSnapshot(version))); + return J.AsTask(GetSnapshot(version)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs index 7c51d63cf..f4f449bce 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs @@ -7,11 +7,13 @@ using System; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; using NodaTime; using Orleans; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; @@ -19,44 +21,90 @@ using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Entities.Rules { public sealed class EventEnricher : IEventEnricher { + private static readonly TimeSpan UserCacheDuration = TimeSpan.FromMinutes(10); private readonly IGrainFactory grainFactory; + private readonly IMemoryCache cache; + private readonly IUserResolver userResolver; private readonly IClock clock; - public EventEnricher(IGrainFactory grainFactory, IClock clock) + public EventEnricher(IGrainFactory grainFactory, IMemoryCache cache, IUserResolver userResolver, IClock clock) { Guard.NotNull(grainFactory, nameof(grainFactory)); + Guard.NotNull(cache, nameof(cache)); Guard.NotNull(clock, nameof(clock)); + Guard.NotNull(userResolver, nameof(userResolver)); - this.grainFactory = grainFactory; - + this.userResolver = userResolver; + this.cache = cache; this.clock = clock; + this.grainFactory = grainFactory; } - public Task EnrichAsync(Envelope @event) + public async Task EnrichAsync(Envelope @event) { Guard.NotNull(@event, nameof(@event)); if (@event.Payload is ContentEvent contentEvent) { - return CreateContentEventAsync(contentEvent, @event); + var result = new EnrichedContentEvent(); + + await Task.WhenAll( + EnrichContentAsync(result, contentEvent, @event), + EnrichDefaultAsync(result, @event)); + + return result; } if (@event.Payload is AssetEvent assetEvent) { + var result = new EnrichedAssetEvent(); + + await Task.WhenAll( + EnrichAssetAsync(result, assetEvent, @event), + EnrichDefaultAsync(result, @event)); + + return result; } - return Task.FromResult(null); + return null; } - private async Task CreateContentEventAsync(ContentEvent contentEvent, Envelope @event) + private async Task EnrichAssetAsync(EnrichedAssetEvent result, AssetEvent assetEvent, Envelope @event) { - var result = new EnrichedContentEvent(); + var asset = + (await grainFactory + .GetGrain(assetEvent.AssetId) + .GetStateAsync(@event.Headers.EventStreamNumber())).Value; + + SimpleMapper.Map(asset, result); + switch (assetEvent) + { + case AssetCreated _: + result.Type = EnrichedAssetEventType.Created; + break; + case AssetRenamed _: + result.Type = EnrichedAssetEventType.Renamed; + break; + case AssetUpdated _: + result.Type = EnrichedAssetEventType.Updated; + break; + case AssetDeleted _: + result.Type = EnrichedAssetEventType.Deleted; + break; + } + + result.Name = $"Asset{result.Type}"; + } + + private async Task EnrichContentAsync(EnrichedContentEvent result, ContentEvent contentEvent, Envelope @event) + { var content = (await grainFactory .GetGrain(contentEvent.ContentId) @@ -68,47 +116,38 @@ namespace Squidex.Domain.Apps.Entities.Rules switch (contentEvent) { - case ContentCreated e: - result.Action = EnrichedContentEventAction.Created; + case ContentCreated _: + result.Type = EnrichedContentEventType.Created; break; - case ContentDeleted e: - result.Action = EnrichedContentEventAction.Deleted; + case ContentDeleted _: + result.Type = EnrichedContentEventType.Deleted; break; - case ContentUpdated e: - result.Action = EnrichedContentEventAction.Updated; + case ContentUpdated _: + result.Type = EnrichedContentEventType.Updated; break; - case ContentStatusChanged e: - if (e.Status == Status.Published) + case ContentStatusChanged contentStatusChanged: + if (contentStatusChanged.Status == Status.Published) { - result.Action = EnrichedContentEventAction.Published; + result.Type = EnrichedContentEventType.Published; } else { - result.Action = EnrichedContentEventAction.Unpublished; + result.Type = EnrichedContentEventType.Unpublished; } break; } - result.Name = $"{content.SchemaId.Name.ToPascalCase()}{result.Action}"; - - SetDefault(result, @event); - - return result; + result.Name = $"{content.SchemaId.Name.ToPascalCase()}{result.Type}"; } - private void SetDefault(EnrichedEvent result, Envelope @event) + private async Task EnrichDefaultAsync(EnrichedEvent result, Envelope @event) { result.Timestamp = @event.Headers.Contains(CommonHeaders.Timestamp) ? @event.Headers.Timestamp() : clock.GetCurrentInstant(); - result.AggregateId = - @event.Headers.Contains(CommonHeaders.AggregateId) ? - @event.Headers.AggregateId() : - Guid.NewGuid(); - if (@event.Payload is SquidexEvent squidexEvent) { result.Actor = squidexEvent.Actor; @@ -118,6 +157,27 @@ namespace Squidex.Domain.Apps.Entities.Rules { result.AppId = appEvent.AppId; } + + result.User = await FindUserAsync(result.Actor); + } + + private Task FindUserAsync(RefToken actor) + { + var key = $"EventEnrichers_Users_${actor.Identifier}"; + + return cache.GetOrCreateAsync(key, async x => + { + x.AbsoluteExpirationRelativeToNow = UserCacheDuration; + + try + { + return await userResolver.FindByIdOrEmailAsync(actor.Identifier); + } + catch + { + return null; + } + }); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 3356a4bbe..aed31f013 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -24,7 +24,7 @@ using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Schemas { - public class SchemaGrain : SquidexDomainObjectGrain, ISchemaGrain + public sealed class SchemaGrain : SquidexDomainObjectGrain, ISchemaGrain { private readonly IAppProvider appProvider; private readonly FieldRegistry registry; 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 9a624c05e..b2186ae40 100644 --- a/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netcoreapp2.1 full 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 bbe33a476..0bec1cd1d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -8,10 +8,7 @@ using System; using System.Collections.Generic; using System.Security.Claims; -using System.Threading.Tasks; using FakeItEasy; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NodaTime; @@ -28,8 +25,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules public class RuleEventFormatterTests { private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); - private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - private readonly IUserResolver userResolver = A.Fake(); private readonly IUser user = A.Fake(); private readonly IRuleUrlGenerator urlGenerator = A.Fake(); private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); @@ -45,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => user.Claims) .Returns(new List { new Claim(SquidexClaimTypes.SquidexDisplayName, "me") }); - sut = new RuleEventFormatter(serializer, urlGenerator, memoryCache, userResolver); + sut = new RuleEventFormatter(serializer, urlGenerator); } [Fact] @@ -57,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules } [Fact] - public void Should_create_route_data() + public void Should_create_payload() { var @event = new EnrichedContentEvent { AppId = appId }; @@ -67,98 +62,79 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules } [Fact] - public void Should_create_route_data_from_event() + public void Should_create_envelope_data_from_event() { var @event = new EnrichedContentEvent { AppId = appId, Name = "MyEventName" }; - var result = sut.ToPayload(@event); + var result = sut.ToEnvelope(@event); Assert.Equal("MyEventName", result["type"]); } [Fact] - public async Task Should_replace_app_information_from_event() + public void Should_replace_app_information_from_event() { var @event = new EnrichedContentEvent { AppId = appId }; - var result = await sut.FormatStringAsync("Name $APP_NAME has id $APP_ID", @event); + var result = sut.Format("Name $APP_NAME has id $APP_ID", @event); Assert.Equal($"Name my-app has id {appId.Id}", result); } [Fact] - public async Task Should_replace_schema_information_from_event() + public void Should_replace_schema_information_from_event() { var @event = new EnrichedContentEvent { SchemaId = schemaId }; - var result = await sut.FormatStringAsync("Name $SCHEMA_NAME has id $SCHEMA_ID", @event); + var result = sut.Format("Name $SCHEMA_NAME has id $SCHEMA_ID", @event); Assert.Equal($"Name my-schema has id {schemaId.Id}", result); } [Fact] - public async Task Should_replace_timestamp_information_from_event() + public void Should_replace_timestamp_information_from_event() { var now = DateTime.UtcNow; var envelope = new EnrichedContentEvent { Timestamp = Instant.FromDateTimeUtc(now) }; - var result = await sut.FormatStringAsync("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); + var result = sut.Format("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result); } [Fact] - public async Task Should_format_email_and_display_name_from_user() + public void Should_format_email_and_display_name_from_user() { - A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) - .Returns(user); - - var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") }; + var @event = new EnrichedContentEvent { User = user, Actor = new RefToken("subject", "123") }; - var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); Assert.Equal($"From me (me@email.com)", result); } [Fact] - public async Task Should_return_undefined_if_user_is_not_found() + public void Should_return_undefined_if_user_is_not_found() { - A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) - .Returns(Task.FromResult(null)); - var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") }; - var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); Assert.Equal($"From UNDEFINED (UNDEFINED)", result); } [Fact] - public async Task Should_return_undefined_if_user_failed_to_resolve() - { - A.CallTo(() => userResolver.FindByIdOrEmailAsync("123")) - .Throws(new InvalidOperationException()); - - var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") }; - - var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event); - - Assert.Equal($"From UNDEFINED (UNDEFINED)", result); - } - - [Fact] - public async Task Should_format_email_and_display_name_from_client() + public void Should_format_email_and_display_name_from_client() { var @event = new EnrichedContentEvent { Actor = new RefToken("client", "android") }; - var result = await sut.FormatStringAsync("From $USER_NAME ($USER_EMAIL)", @event); + var result = sut.Format("From $USER_NAME ($USER_EMAIL)", @event); Assert.Equal($"From client:android (client:android)", result); } [Fact] - public async Task Should_replace_content_url_from_event() + public void Should_replace_content_url_from_event() { var url = "http://content"; @@ -167,13 +143,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId }; - var result = await sut.FormatStringAsync("Go to $CONTENT_URL", @event); + var result = sut.Format("Go to $CONTENT_URL", @event); Assert.Equal($"Go to {url}", result); } [Fact] - public async Task Should_return_undefined_when_field_not_found() + public void Should_format_content_url_when_not_found() + { + Assert.Equal("UNDEFINED", sut.Format("$CONTENT_URL", new EnrichedAssetEvent())); + } + + [Fact] + public void Should_return_undefined_when_field_not_found() { var @event = new EnrichedContentEvent { @@ -184,13 +166,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.country.iv", @event); + var result = sut.Format("$CONTENT_DATA.country.iv", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_undefined_when_partition_not_found() + public void Should_return_undefined_when_partition_not_found() { var @event = new EnrichedContentEvent { @@ -201,13 +183,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de", @event); + var result = sut.Format("$CONTENT_DATA.city.de", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_undefined_when_array_item_not_found() + public void Should_return_undefined_when_array_item_not_found() { var @event = new EnrichedContentEvent { @@ -218,13 +200,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", new JArray())) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.10", @event); + var result = sut.Format("$CONTENT_DATA.city.de.10", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_undefined_when_property_not_found() + public void Should_return_undefined_when_property_not_found() { var @event = new EnrichedContentEvent { @@ -236,13 +218,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.de.Name", @event); + var result = sut.Format("$CONTENT_DATA.city.de.Name", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_plain_value_when_found() + public void Should_return_plain_value_when_found() { var @event = new EnrichedContentEvent { @@ -253,13 +235,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event); + var result = sut.Format("$CONTENT_DATA.city.iv", @event); Assert.Equal("Berlin", result); } [Fact] - public async Task Should_return_plain_value_when_found_from_update_event() + public void Should_return_plain_value_when_found_from_update_event() { var @event = new EnrichedContentEvent { @@ -270,13 +252,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", "Berlin")) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event); + var result = sut.Format("$CONTENT_DATA.city.iv", @event); Assert.Equal("Berlin", result); } [Fact] - public async Task Should_return_undefined_when_null() + public void Should_return_undefined_when_null() { var @event = new EnrichedContentEvent { @@ -287,13 +269,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JValue.CreateNull())) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event); + var result = sut.Format("$CONTENT_DATA.city.iv", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_undefined_when_undefined() + public void Should_return_undefined_when_undefined() { var @event = new EnrichedContentEvent { @@ -304,13 +286,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .AddValue("iv", JValue.CreateUndefined())) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event); + var result = sut.Format("$CONTENT_DATA.city.iv", @event); Assert.Equal("UNDEFINED", result); } [Fact] - public async Task Should_return_string_when_object() + public void Should_return_string_when_object() { var @event = new EnrichedContentEvent { @@ -322,13 +304,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv", @event); + var result = sut.Format("$CONTENT_DATA.city.iv", @event); Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); } [Fact] - public async Task Should_return_plain_value_from_array_when_found() + public void Should_return_plain_value_from_array_when_found() { var @event = new EnrichedContentEvent { @@ -340,13 +322,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules "Berlin"))) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.0", @event); + var result = sut.Format("$CONTENT_DATA.city.iv.0", @event); Assert.Equal("Berlin", result); } [Fact] - public async Task Should_return_plain_value_from_object_when_found() + public void Should_return_plain_value_from_object_when_found() { var @event = new EnrichedContentEvent { @@ -358,18 +340,21 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new JProperty("name", "Berlin")))) }; - var result = await sut.FormatStringAsync("$CONTENT_DATA.city.iv.name", @event); + var result = sut.Format("$CONTENT_DATA.city.iv.name", @event); Assert.Equal("Berlin", result); } [Fact] - public async Task Should_format_content_actions_when_found() + public void Should_format_content_actions_when_found() + { + Assert.Equal("created", sut.Format("$CONTENT_ACTION", new EnrichedContentEvent { Type = EnrichedContentEventType.Created })); + } + + [Fact] + public void Should_format_content_actions_when_not_found() { - Assert.Equal("created", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Created })); - Assert.Equal("updated", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Updated })); - Assert.Equal("deleted", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Deleted })); - Assert.Equal("archived", await sut.FormatStringAsync("$CONTENT_ACTION", new EnrichedContentEvent { Action = EnrichedContentEventAction.Archived })); + Assert.Equal("UNDEFINED", sut.Format("$CONTENT_ACTION", new EnrichedAssetEvent())); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs new file mode 100644 index 000000000..9b0c35c1c --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs @@ -0,0 +1,72 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Orleans; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Orleans; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public class ContentVersionLoaderTests + { + private readonly IGrainFactory grainFactory = A.Fake(); + private readonly IContentGrain grain = A.Fake(); + private readonly Guid id = Guid.NewGuid(); + private readonly ContentVersionLoader sut; + + public ContentVersionLoaderTests() + { + A.CallTo(() => grainFactory.GetGrain(id, null)) + .Returns(grain); + + sut = new ContentVersionLoader(grainFactory); + } + + [Fact] + public async Task Should_throw_exception_if_no_state_returned() + { + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(new J(null)); + + await Assert.ThrowsAsync(() => sut.LoadAsync(id, 10)); + } + + [Fact] + public async Task Should_throw_exception_if_state_has_other_version() + { + var entity = A.Fake(); + + A.CallTo(() => entity.Version) + .Returns(5); + + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(J.Of(entity)); + + await Assert.ThrowsAsync(() => sut.LoadAsync(id, 10)); + } + + [Fact] + public async Task Should_return_content_from_state() + { + var entity = A.Fake(); + + A.CallTo(() => entity.Version) + .Returns(10); + + A.CallTo(() => grain.GetStateAsync(10)) + .Returns(J.Of(entity)); + + var result = await sut.LoadAsync(id, 10); + + Assert.Same(entity, result); + } + } +} diff --git a/tools/Migrate_01/Migrate_01.csproj b/tools/Migrate_01/Migrate_01.csproj index 27d9fd97a..371f76a9b 100644 --- a/tools/Migrate_01/Migrate_01.csproj +++ b/tools/Migrate_01/Migrate_01.csproj @@ -1,6 +1,6 @@ - + - netstandard2.0 + netcoreapp2.1