Browse Source

Formatter improved.

pull/306/head
Sebastian 8 years ago
parent
commit
2b0237dadd
  1. 8
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs
  2. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs
  3. 10
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs
  4. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs
  5. 14
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs
  6. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs
  7. 4
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  8. 47
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEvent.cs
  9. 17
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedAssetEventType.cs
  10. 7
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEvent.cs
  11. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs
  12. 15
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs
  13. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs
  14. 12
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  15. 257
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  16. 2
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  17. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  18. 2
      src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
  19. 6
      src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs
  20. 4
      src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs
  21. 4
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  22. 118
      src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs
  23. 2
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  24. 2
      src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj
  25. 125
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  26. 72
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentVersionLoaderTests.cs
  27. 4
      tools/Migrate_01/Migrate_01.csproj

8
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) if (@event is EnrichedContentEvent contentEvent)
{ {
@ -64,11 +64,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
AppId = action.AppId, AppId = action.AppId,
ApiKey = action.ApiKey, ApiKey = action.ApiKey,
ContentId = contentId, ContentId = contentId,
IndexName = await formatter.FormatStringAsync(action.IndexName, @event) IndexName = formatter.Format(action.IndexName, @event)
}; };
if (contentEvent.Action == EnrichedContentEventAction.Deleted || if (contentEvent.Type == EnrichedContentEventType.Deleted ||
contentEvent.Action == EnrichedContentEventAction.Unpublished) contentEvent.Type == EnrichedContentEventType.Unpublished)
{ {
ruleDescription = $"Delete entry from Algolia index: {action.IndexName}"; ruleDescription = $"Delete entry from Algolia index: {action.IndexName}";
} }

4
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 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 ruleDescription = $"Send AzureQueueJob to azure queue '{action.Queue}'";
var ruleJob = new AzureQueueJob var ruleJob = new AzureQueueJob

10
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) if (@event is EnrichedContentEvent contentEvent)
{ {
@ -73,12 +73,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
Username = action.Username, Username = action.Username,
Password = action.Password, Password = action.Password,
ContentId = contentId, ContentId = contentId,
IndexName = await formatter.FormatStringAsync(action.IndexName, @event), IndexName = formatter.Format(action.IndexName, @event),
IndexType = await formatter.FormatStringAsync(action.IndexType, @event), IndexType = formatter.Format(action.IndexType, @event),
}; };
if (contentEvent.Action == EnrichedContentEventAction.Deleted || if (contentEvent.Type == EnrichedContentEventType.Deleted ||
contentEvent.Action == EnrichedContentEventAction.Unpublished) contentEvent.Type == EnrichedContentEventType.Unpublished)
{ {
ruleDescription = $"Delete entry index: {action.IndexName}"; ruleDescription = $"Delete entry index: {action.IndexName}";
} }

4
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"; 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 var ruleJob = new FastlyJob
{ {
@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
FastlyServiceID = action.ServiceId FastlyServiceID = action.ServiceId
}; };
return Task.FromResult((Description, ruleJob)); return (Description, ruleJob);
} }
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(FastlyJob job)

14
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; 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 = var requestUrl =
!string.IsNullOrWhiteSpace(action.Author) ? !string.IsNullOrWhiteSpace(action.Author) ?
@ -51,11 +51,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
var requestBody = var requestBody =
new JObject( 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("contentFormat", action.IsHtml ? "html" : "markdown"),
new JProperty("content", await formatter.FormatStringAsync(action.Content, @event)), new JProperty("content", formatter.Format(action.Content, @event)),
new JProperty("canonicalUrl", await formatter.FormatStringAsync(action.CanonicalUrl, @event)), new JProperty("canonicalUrl", formatter.Format(action.CanonicalUrl, @event)),
new JProperty("tags", await ParseTagsAsync(@event, action))); new JProperty("tags", ParseTags(@event, action)));
var ruleJob = new MediumJob var ruleJob = new MediumJob
{ {
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
return (Description, ruleJob); return (Description, ruleJob);
} }
private async Task<JArray> ParseTagsAsync(EnrichedEvent @event, MediumAction action) private JArray ParseTags(EnrichedEvent @event, MediumAction action)
{ {
if (string.IsNullOrWhiteSpace(action.Tags)) if (string.IsNullOrWhiteSpace(action.Tags))
{ {
@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
string[] tags; string[] tags;
try try
{ {
var jsonTags = await formatter.FormatStringAsync(action.Tags, @event); var jsonTags = formatter.Format(action.Tags, @event);
tags = JsonConvert.DeserializeObject<string[]>(jsonTags); tags = JsonConvert.DeserializeObject<string[]>(jsonTags);
} }

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs

@ -49,11 +49,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; 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 = var body =
new JObject( new JObject(
new JProperty("text", await formatter.FormatStringAsync(action.Text, @event))); new JProperty("text", formatter.Format(action.Text, @event)));
var ruleJob = new SlackJob var ruleJob = new SlackJob
{ {

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -50,10 +50,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Actions
this.formatter = formatter; 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 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 ruleDescription = $"Send event to webhook '{requestUrl}'";
var ruleJob = new WebhookJob var ruleJob = new WebhookJob

47
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; }
}
}
}

17
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
}
}

7
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 sealed class EnrichedContentEvent : EnrichedSchemaEvent
{ {
public EnrichedContentEventAction Action { get; set; } public EnrichedContentEventType Type { get; set; }
public Guid Id { get; set; } public Guid Id { get; set; }
@ -29,5 +29,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
public NamedContentData Data { get; set; } public NamedContentData Data { get; set; }
public Status Status { get; set; } public Status Status { get; set; }
public override Guid AggregateId
{
get { return Id; }
}
} }
} }

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventAction.cs → src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{ {
public enum EnrichedContentEventAction public enum EnrichedContentEventType
{ {
Created, Created,
Deleted, Deleted,

15
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs

@ -6,21 +6,30 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{ {
public class EnrichedEvent public abstract class EnrichedEvent
{ {
public Guid AggregateId { get; set; }
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
public RefToken Actor { get; set; } public RefToken Actor { get; set; }
public Instant Timestamp { get; set; } public Instant Timestamp { get; set; }
public long Version { get; set; }
[JsonIgnore]
public abstract Guid AggregateId { get; }
[JsonIgnore]
public string Name { get; set; } public string Name { get; set; }
[JsonIgnore]
public IUser User { get; set; }
} }
} }

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedSchemaEvent.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{ {
public class EnrichedSchemaEvent : EnrichedEvent public abstract class EnrichedSchemaEvent : EnrichedEvent
{ {
public NamedId<Guid> SchemaId { get; set; } public NamedId<Guid> SchemaId { get; set; }
} }

12
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.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
#pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler where TAction : RuleAction public abstract class RuleActionHandler<TAction, TData> : IRuleActionHandler where TAction : RuleAction
@ -34,7 +36,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return await ExecuteJobAsync(typedData); 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); protected abstract Task<(string Dump, Exception Exception)> ExecuteJobAsync(TData job);
} }

257
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -6,17 +6,15 @@
// =========================================-================================= // =========================================-=================================
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
@ -24,42 +22,38 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public class RuleEventFormatter public class RuleEventFormatter
{ {
private const string Undefined = "UNDEFINED"; private const string Undefined = "UNDEFINED";
private const string AppIdPlaceholder = "$APP_ID"; private static readonly Regex ContentDataPlaceholder = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
private const string AppNamePlaceholder = "$APP_NAME"; private static readonly Regex ContentDataPlaceholder2 = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled);
private const string SchemaIdPlaceholder = "$SCHEMA_ID"; private readonly List<(string Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(string Pattern, Func<EnrichedEvent, string> Replacer)>();
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 readonly JsonSerializer serializer; private readonly JsonSerializer serializer;
private readonly IRuleUrlGenerator urlGenerator; private readonly IRuleUrlGenerator urlGenerator;
private readonly IMemoryCache memoryCache;
private readonly IUserResolver userResolver;
public RuleEventFormatter( public RuleEventFormatter(JsonSerializer serializer, IRuleUrlGenerator urlGenerator)
JsonSerializer serializer,
IRuleUrlGenerator urlGenerator,
IMemoryCache memoryCache,
IUserResolver userResolver)
{ {
Guard.NotNull(memoryCache, nameof(memoryCache));
Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(urlGenerator, nameof(urlGenerator)); Guard.NotNull(urlGenerator, nameof(urlGenerator));
Guard.NotNull(userResolver, nameof(userResolver));
this.memoryCache = memoryCache;
this.serializer = serializer; this.serializer = serializer;
this.userResolver = userResolver;
this.urlGenerator = urlGenerator; 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<EnrichedEvent, string> generator)
{
patterns.Add((placeholder, generator));
}
public virtual JObject ToPayload<T>(T @event)
{ {
return JObject.FromObject(@event, serializer); return JObject.FromObject(@event, serializer);
} }
@ -67,102 +61,184 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public virtual JObject ToEnvelope(EnrichedEvent @event) public virtual JObject ToEnvelope(EnrichedEvent @event)
{ {
return new JObject( return new JObject(
new JProperty("type", @event), new JProperty("type", @event.Name),
new JProperty("payload", ToPayload(@event)), new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Timestamp.ToString())); new JProperty("timestamp", @event.Timestamp.ToString()));
} }
public async virtual Task<string> FormatStringAsync(string text, EnrichedEvent @event) public string Format(string text, EnrichedEvent @event)
{ {
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
return 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)); var sb = new StringBuilder();
sb.Replace(TimestampDatePlaceholder, @event.Timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture));
if (@event.AppId != null) for (var i = 0; i < current.Length; i++)
{ {
sb.Replace(AppIdPlaceholder, @event.AppId.Id.ToString()); var c = current[i];
sb.Replace(AppNamePlaceholder, @event.AppId.Name);
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()); return schemaEvent.SchemaId.Id.ToString();
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name);
} }
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)); return schemaEvent.SchemaId.Name;
sb.Replace(ContentActionPlaceholder, contentEvent.Action.ToString());
} }
await FormatUserInfoAsync(@event, sb); return Undefined;
}
var result = sb.ToString();
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 (@event is EnrichedContentEvent contentEvent)
if (text.Contains(UserEmailPlaceholder) || text.Contains(UserNamePlaceholder))
{ {
var actor = @event.Actor; return urlGenerator.GenerateContentUIUrl(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id);
}
if (actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase)) return Undefined;
{ }
var displayText = actor.ToString();
sb.Replace(UserEmailPlaceholder, displayText); private static string UserName(EnrichedEvent @event)
sb.Replace(UserNamePlaceholder, displayText); {
} if (@event.Actor != null)
else {
if (@event.Actor.Type.Equals("client", StringComparison.OrdinalIgnoreCase))
{ {
var user = await FindUserAsync(actor); return @event.Actor.ToString();
}
if (user != null) if (@event.User != null)
{ {
sb.Replace(UserEmailPlaceholder, user.Email); return @event.User.DisplayName();
sb.Replace(UserNamePlaceholder, user.DisplayName());
}
else
{
sb.Replace(UserEmailPlaceholder, Undefined);
sb.Replace(UserNamePlaceholder, Undefined);
}
} }
} }
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 => if (@event.User != null)
{ {
return Replace(data, match); 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; var captures = match.Groups[2].Captures;
@ -212,24 +288,5 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return value?.ToString(Formatting.Indented) ?? Undefined; return value?.ToString(Formatting.Indented) ?? Undefined;
} }
private Task<IUser> 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;
}
});
}
} }
} }

2
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Squidex.Domain.Apps.Core</RootNamespace> <RootNamespace>Squidex.Domain.Apps.Core</RootNamespace>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

2
src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>

2
src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs

@ -26,7 +26,7 @@ using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public class AppGrain : SquidexDomainObjectGrain<AppState>, IAppGrain public sealed class AppGrain : SquidexDomainObjectGrain<AppState>, IAppGrain
{ {
private readonly InitialPatterns initialPatterns; private readonly InitialPatterns initialPatterns;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;

6
src/Squidex.Domain.Apps.Entities/Assets/AssetGrain.cs

@ -22,7 +22,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public class AssetGrain : SquidexDomainObjectGrain<AssetState>, IAssetGrain public sealed class AssetGrain : SquidexDomainObjectGrain<AssetState>, IAssetGrain
{ {
public AssetGrain(IStore<Guid> store, ISemanticLog log) public AssetGrain(IStore<Guid> store, ISemanticLog log)
: base(store, log) : base(store, log)
@ -140,9 +140,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
ApplySnapshot(Snapshot.Apply(@event)); ApplySnapshot(Snapshot.Apply(@event));
} }
public Task<J<IAssetEntity>> GetStateAsync() public Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Empty)
{ {
return J.AsTask<IAssetEntity>(Snapshot); return J.AsTask<IAssetEntity>(GetSnapshot(version));
} }
} }
} }

4
src/Squidex.Domain.Apps.Entities/Assets/IAssetGrain.cs

@ -5,11 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetGrain : IDomainObjectGrain public interface IAssetGrain : IDomainObjectGrain
{ {
Task<J<IAssetEntity>> GetStateAsync(long version = EtagVersion.Any);
} }
} }

4
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -26,7 +26,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public class ContentGrain : SquidexDomainObjectGrain<ContentState>, IContentGrain public sealed class ContentGrain : SquidexDomainObjectGrain<ContentState>, IContentGrain
{ {
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IAssetRepository assetRepository; private readonly IAssetRepository assetRepository;
@ -293,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public Task<J<IContentEntity>> GetStateAsync(long version = -2) public Task<J<IContentEntity>> GetStateAsync(long version = -2)
{ {
return Task.FromResult(J.Of<IContentEntity>(GetSnapshot(version))); return J.AsTask<IContentEntity>(GetSnapshot(version));
} }
} }
} }

118
src/Squidex.Domain.Apps.Entities/Rules/EventEnricher.cs

@ -7,11 +7,13 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using NodaTime; using NodaTime;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
@ -19,44 +21,90 @@ using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Entities.Rules namespace Squidex.Domain.Apps.Entities.Rules
{ {
public sealed class EventEnricher : IEventEnricher public sealed class EventEnricher : IEventEnricher
{ {
private static readonly TimeSpan UserCacheDuration = TimeSpan.FromMinutes(10);
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IMemoryCache cache;
private readonly IUserResolver userResolver;
private readonly IClock clock; 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(grainFactory, nameof(grainFactory));
Guard.NotNull(cache, nameof(cache));
Guard.NotNull(clock, nameof(clock)); Guard.NotNull(clock, nameof(clock));
Guard.NotNull(userResolver, nameof(userResolver));
this.grainFactory = grainFactory; this.userResolver = userResolver;
this.cache = cache;
this.clock = clock; this.clock = clock;
this.grainFactory = grainFactory;
} }
public Task<EnrichedEvent> EnrichAsync(Envelope<AppEvent> @event) public async Task<EnrichedEvent> EnrichAsync(Envelope<AppEvent> @event)
{ {
Guard.NotNull(@event, nameof(@event)); Guard.NotNull(@event, nameof(@event));
if (@event.Payload is ContentEvent contentEvent) 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) 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<EnrichedEvent>(null); return null;
} }
private async Task<EnrichedEvent> CreateContentEventAsync(ContentEvent contentEvent, Envelope<AppEvent> @event) private async Task EnrichAssetAsync(EnrichedAssetEvent result, AssetEvent assetEvent, Envelope<AppEvent> @event)
{ {
var result = new EnrichedContentEvent(); var asset =
(await grainFactory
.GetGrain<IAssetGrain>(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<AppEvent> @event)
{
var content = var content =
(await grainFactory (await grainFactory
.GetGrain<IContentGrain>(contentEvent.ContentId) .GetGrain<IContentGrain>(contentEvent.ContentId)
@ -68,47 +116,38 @@ namespace Squidex.Domain.Apps.Entities.Rules
switch (contentEvent) switch (contentEvent)
{ {
case ContentCreated e: case ContentCreated _:
result.Action = EnrichedContentEventAction.Created; result.Type = EnrichedContentEventType.Created;
break; break;
case ContentDeleted e: case ContentDeleted _:
result.Action = EnrichedContentEventAction.Deleted; result.Type = EnrichedContentEventType.Deleted;
break; break;
case ContentUpdated e: case ContentUpdated _:
result.Action = EnrichedContentEventAction.Updated; result.Type = EnrichedContentEventType.Updated;
break; break;
case ContentStatusChanged e: case ContentStatusChanged contentStatusChanged:
if (e.Status == Status.Published) if (contentStatusChanged.Status == Status.Published)
{ {
result.Action = EnrichedContentEventAction.Published; result.Type = EnrichedContentEventType.Published;
} }
else else
{ {
result.Action = EnrichedContentEventAction.Unpublished; result.Type = EnrichedContentEventType.Unpublished;
} }
break; break;
} }
result.Name = $"{content.SchemaId.Name.ToPascalCase()}{result.Action}"; result.Name = $"{content.SchemaId.Name.ToPascalCase()}{result.Type}";
SetDefault(result, @event);
return result;
} }
private void SetDefault(EnrichedEvent result, Envelope<AppEvent> @event) private async Task EnrichDefaultAsync(EnrichedEvent result, Envelope<AppEvent> @event)
{ {
result.Timestamp = result.Timestamp =
@event.Headers.Contains(CommonHeaders.Timestamp) ? @event.Headers.Contains(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() : @event.Headers.Timestamp() :
clock.GetCurrentInstant(); clock.GetCurrentInstant();
result.AggregateId =
@event.Headers.Contains(CommonHeaders.AggregateId) ?
@event.Headers.AggregateId() :
Guid.NewGuid();
if (@event.Payload is SquidexEvent squidexEvent) if (@event.Payload is SquidexEvent squidexEvent)
{ {
result.Actor = squidexEvent.Actor; result.Actor = squidexEvent.Actor;
@ -118,6 +157,27 @@ namespace Squidex.Domain.Apps.Entities.Rules
{ {
result.AppId = appEvent.AppId; result.AppId = appEvent.AppId;
} }
result.User = await FindUserAsync(result.Actor);
}
private Task<IUser> 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;
}
});
} }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -24,7 +24,7 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
{ {
public class SchemaGrain : SquidexDomainObjectGrain<SchemaState>, ISchemaGrain public sealed class SchemaGrain : SquidexDomainObjectGrain<SchemaState>, ISchemaGrain
{ {
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly FieldRegistry registry; private readonly FieldRegistry registry;

2
src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>

125
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -8,10 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
@ -28,8 +25,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
public class RuleEventFormatterTests public class RuleEventFormatterTests
{ {
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); private readonly JsonSerializer serializer = JsonSerializer.CreateDefault();
private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly IUserResolver userResolver = A.Fake<IUserResolver>();
private readonly IUser user = A.Fake<IUser>(); private readonly IUser user = A.Fake<IUser>();
private readonly IRuleUrlGenerator urlGenerator = A.Fake<IRuleUrlGenerator>(); private readonly IRuleUrlGenerator urlGenerator = A.Fake<IRuleUrlGenerator>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
@ -45,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => user.Claims) A.CallTo(() => user.Claims)
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.SquidexDisplayName, "me") }); .Returns(new List<Claim> { new Claim(SquidexClaimTypes.SquidexDisplayName, "me") });
sut = new RuleEventFormatter(serializer, urlGenerator, memoryCache, userResolver); sut = new RuleEventFormatter(serializer, urlGenerator);
} }
[Fact] [Fact]
@ -57,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
} }
[Fact] [Fact]
public void Should_create_route_data() public void Should_create_payload()
{ {
var @event = new EnrichedContentEvent { AppId = appId }; var @event = new EnrichedContentEvent { AppId = appId };
@ -67,98 +62,79 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
} }
[Fact] [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 @event = new EnrichedContentEvent { AppId = appId, Name = "MyEventName" };
var result = sut.ToPayload(@event); var result = sut.ToEnvelope(@event);
Assert.Equal("MyEventName", result["type"]); Assert.Equal("MyEventName", result["type"]);
} }
[Fact] [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 @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); Assert.Equal($"Name my-app has id {appId.Id}", result);
} }
[Fact] [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 @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); Assert.Equal($"Name my-schema has id {schemaId.Id}", result);
} }
[Fact] [Fact]
public async Task Should_replace_timestamp_information_from_event() public void Should_replace_timestamp_information_from_event()
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var envelope = new EnrichedContentEvent { Timestamp = Instant.FromDateTimeUtc(now) }; 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); Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result);
} }
[Fact] [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")) var @event = new EnrichedContentEvent { User = user, Actor = new RefToken("subject", "123") };
.Returns(user);
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 me (me@email.com)", result); Assert.Equal($"From me (me@email.com)", result);
} }
[Fact] [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<IUser>(null));
var @event = new EnrichedContentEvent { Actor = new RefToken("subject", "123") }; 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); Assert.Equal($"From UNDEFINED (UNDEFINED)", result);
} }
[Fact] [Fact]
public async Task Should_return_undefined_if_user_failed_to_resolve() public void Should_format_email_and_display_name_from_client()
{
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()
{ {
var @event = new EnrichedContentEvent { Actor = new RefToken("client", "android") }; 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); Assert.Equal($"From client:android (client:android)", result);
} }
[Fact] [Fact]
public async Task Should_replace_content_url_from_event() public void Should_replace_content_url_from_event()
{ {
var url = "http://content"; 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 @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); Assert.Equal($"Go to {url}", result);
} }
[Fact] [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 var @event = new EnrichedContentEvent
{ {
@ -184,13 +166,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public async Task Should_return_undefined_when_partition_not_found() public void Should_return_undefined_when_partition_not_found()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -201,13 +183,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [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 var @event = new EnrichedContentEvent
{ {
@ -218,13 +200,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", new JArray())) .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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public async Task Should_return_undefined_when_property_not_found() public void Should_return_undefined_when_property_not_found()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -236,13 +218,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) 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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public async Task Should_return_plain_value_when_found() public void Should_return_plain_value_when_found()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -253,13 +235,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .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); Assert.Equal("Berlin", result);
} }
[Fact] [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 var @event = new EnrichedContentEvent
{ {
@ -270,13 +252,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", "Berlin")) .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); Assert.Equal("Berlin", result);
} }
[Fact] [Fact]
public async Task Should_return_undefined_when_null() public void Should_return_undefined_when_null()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -287,13 +269,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateNull())) .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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public async Task Should_return_undefined_when_undefined() public void Should_return_undefined_when_undefined()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -304,13 +286,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
.AddValue("iv", JValue.CreateUndefined())) .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); Assert.Equal("UNDEFINED", result);
} }
[Fact] [Fact]
public async Task Should_return_string_when_object() public void Should_return_string_when_object()
{ {
var @event = new EnrichedContentEvent var @event = new EnrichedContentEvent
{ {
@ -322,13 +304,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) 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); Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result);
} }
[Fact] [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 var @event = new EnrichedContentEvent
{ {
@ -340,13 +322,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
"Berlin"))) "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); Assert.Equal("Berlin", result);
} }
[Fact] [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 var @event = new EnrichedContentEvent
{ {
@ -358,18 +340,21 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
new JProperty("name", "Berlin")))) 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); Assert.Equal("Berlin", result);
} }
[Fact] [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("UNDEFINED", sut.Format("$CONTENT_ACTION", new EnrichedAssetEvent()));
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 }));
} }
} }
} }

72
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<IGrainFactory>();
private readonly IContentGrain grain = A.Fake<IContentGrain>();
private readonly Guid id = Guid.NewGuid();
private readonly ContentVersionLoader sut;
public ContentVersionLoaderTests()
{
A.CallTo(() => grainFactory.GetGrain<IContentGrain>(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<IContentEntity>(null));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
[Fact]
public async Task Should_throw_exception_if_state_has_other_version()
{
var entity = A.Fake<IContentEntity>();
A.CallTo(() => entity.Version)
.Returns(5);
A.CallTo(() => grain.GetStateAsync(10))
.Returns(J.Of(entity));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(id, 10));
}
[Fact]
public async Task Should_return_content_from_state()
{
var entity = A.Fake<IContentEntity>();
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);
}
}
}

4
tools/Migrate_01/Migrate_01.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" /> <ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" />

Loading…
Cancel
Save