Browse Source

Merge pull request #335 from Squidex/feature-json2

Feature json2
pull/337/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
0110532bd0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs
  2. 19
      extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  3. 14
      extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  4. 53
      extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs
  5. 9
      extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs
  6. 8
      extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs
  7. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs
  8. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs
  9. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs
  10. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs
  11. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs
  12. 4
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  13. 22
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  14. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs
  15. 5
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs
  16. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs
  17. 19
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  18. 6
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs
  19. 17
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  20. 4
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
  21. 22
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  22. 11
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs
  23. 50
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs
  24. 4
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  25. 36
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  26. 27
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  27. 12
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  28. 4
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
  29. 6
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs
  30. 7
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs
  31. 28
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  32. 38
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  33. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  34. 8
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  35. 94
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  36. 1
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  37. 34
      src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs
  38. 5
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  39. 175
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  40. 7
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  41. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
  42. 4
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs
  43. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  44. 11
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  45. 9
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
  46. 7
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  47. 7
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs
  48. 11
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  49. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  50. 18
      src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  51. 19
      src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  52. 8
      src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs
  53. 18
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  54. 7
      src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs
  55. 24
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  56. 7
      src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs
  57. 72
      src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  58. 15
      src/Squidex.Domain.Apps.Entities/Backup/BackupVersion.cs
  59. 41
      src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs
  60. 118
      src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs
  61. 5
      src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs
  62. 116
      src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs
  63. 23
      src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  64. 4
      src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs
  65. 14
      src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs
  66. 4
      src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs
  67. 21
      src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs
  68. 13
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  69. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  70. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs
  71. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs
  72. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs
  73. 6
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs
  74. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs
  75. 41
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantGraphType.cs
  76. 25
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantValue.cs
  77. 6
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs
  78. 8
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs
  79. 18
      src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs
  80. 14
      src/Squidex.Domain.Apps.Entities/DomainObjectState.cs
  81. 8
      src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs
  82. 28
      src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  83. 2
      src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs
  84. 2
      src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs
  85. 5
      src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs
  86. 21
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs
  87. 14
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs
  88. 9
      src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs
  89. 11
      src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs
  90. 155
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs
  91. 20
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs
  92. 5
      src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs
  93. 12
      src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs
  94. 9
      src/Squidex.Infrastructure.Redis/RedisPubSub.cs
  95. 10
      src/Squidex.Infrastructure.Redis/RedisSubscription.cs
  96. 2
      src/Squidex.Infrastructure/Assets/AssetFile.cs
  97. 2
      src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs
  98. 34
      src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs
  99. 79
      src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  100. 25
      src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs

4
extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs

@ -56,7 +56,9 @@ namespace Squidex.Extensions.Actions.Algolia
{
ruleDescription = $"Add entry to Algolia index: {action.IndexName}";
ruleJob.Content = ToPayload(contentEvent);
var json = ToJson(contentEvent);
ruleJob.Content = JObject.Parse(json);
ruleJob.Content["objectID"] = contentId;
}

19
extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs

@ -6,10 +6,10 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -32,25 +32,28 @@ namespace Squidex.Extensions.Actions.Discourse
{
var url = $"{action.Url.ToString().TrimEnd('/')}/posts.json?api_key={action.ApiKey}&api_username={action.ApiUsername}";
var json =
new JObject(
new JProperty("raw", Format(action.Text, @event)),
new JProperty("title", Format(action.Title, @event)));
var json = new Dictionary<string, object>
{
["raw"] = Format(action.Text, @event),
["title"] = Format(action.Title, @event)
};
if (action.Topic.HasValue)
{
json.Add(new JProperty("topic_id", action.Topic.Value));
json.Add("topic_id", action.Topic.Value);
}
if (action.Category.HasValue)
{
json.Add(new JProperty("category", action.Category.Value));
json.Add("category", action.Category.Value);
}
var requestBody = ToJson(json);
var ruleJob = new DiscourseJob
{
RequestUrl = url,
RequestBody = json.ToString()
RequestBody = requestBody
};
var description =

14
extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -8,7 +8,6 @@
using System;
using System.Threading.Tasks;
using Elasticsearch.Net;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -60,8 +59,9 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{
ruleDescription = $"Upsert to index: {action.IndexName}";
ruleJob.Content = ToPayload(contentEvent);
ruleJob.Content["objectID"] = contentId;
var json = ToJson(contentEvent);
ruleJob.Content = $"{{ \"objectId\": \"{contentId}\", {json.Substring(1)}";
}
ruleJob.Username = action.Username;
@ -86,9 +86,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{
if (job.Content != null)
{
var doc = job.Content.ToString();
var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, job.Content);
return (response.Body, response.OriginalException);
}
@ -116,10 +114,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch
public string ContentId { get; set; }
public string Content { get; set; }
public string IndexName { get; set; }
public string IndexType { get; set; }
public JObject Content { get; set; }
}
}

53
extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs

@ -9,11 +9,10 @@ using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure.Http;
using Squidex.Infrastructure.Json;
namespace Squidex.Extensions.Actions.Medium
{
@ -22,53 +21,61 @@ namespace Squidex.Extensions.Actions.Medium
private const string Description = "Post to medium";
private readonly IHttpClientFactory httpClientFactory;
private readonly IJsonSerializer serializer;
public MediumActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
private sealed class UserResponse
{
public UserResponseData Data { get; set; }
}
private sealed class UserResponseData
{
public string Id { get; set; }
}
public MediumActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory, IJsonSerializer serializer)
: base(formatter)
{
this.httpClientFactory = httpClientFactory;
this.serializer = serializer;
}
protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action)
{
var requestBody =
new JObject(
new JProperty("title", Format(action.Title, @event)),
new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"),
new JProperty("content", Format(action.Content, @event)),
new JProperty("canonicalUrl", Format(action.CanonicalUrl, @event)),
new JProperty("tags", ParseTags(@event, action)));
var ruleJob = new MediumJob
var ruleJob = new MediumJob { AccessToken = action.AccessToken, PublicationId = action.PublicationId };
var requestBody = new
{
AccessToken = action.AccessToken,
PublicationId = action.PublicationId,
RequestBody = requestBody.ToString(Formatting.Indented)
title = Format(action.Title, @event),
contentFormat = action.IsHtml ? "html" : "markdown",
content = Format(action.Content, @event),
canonicalUrl = Format(action.CanonicalUrl, @event),
tags = ParseTags(@event, action)
};
ruleJob.RequestBody = ToJson(requestBody);
return (Description, ruleJob);
}
private JArray ParseTags(EnrichedEvent @event, MediumAction action)
private string[] ParseTags(EnrichedEvent @event, MediumAction action)
{
if (string.IsNullOrWhiteSpace(action.Tags))
{
return null;
}
string[] tags;
try
{
var jsonTags = Format(action.Tags, @event);
tags = JsonConvert.DeserializeObject<string[]>(jsonTags);
return serializer.Deserialize<string[]>(jsonTags);
}
catch
{
tags = action.Tags.Split(',');
return action.Tags.Split(',');
}
return new JArray(tags);
}
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job)
@ -96,9 +103,9 @@ namespace Squidex.Extensions.Actions.Medium
response = await httpClient.SendAsync(meRequest);
var responseString = await response.Content.ReadAsStringAsync();
var responseJson = JToken.Parse(responseString);
var responseJson = serializer.Deserialize<UserResponse>(responseString);
var id = responseJson["data"]["id"].ToString();
var id = responseJson.Data?.Id;
path = $"v1/users/{id}/posts";
}

9
extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs

@ -9,7 +9,6 @@ using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -29,12 +28,10 @@ namespace Squidex.Extensions.Actions.Prerender
{
var url = Format(action.Url, @event);
var request =
new JObject(
new JProperty("prerenderToken", action.Token),
new JProperty("url", url));
var request = new { prerenderToken = action.Token, url };
var requestBody = ToJson(request);
return ($"Recache {url}", new PrerenderJob { RequestBody = request.ToString() });
return ($"Recache {url}", new PrerenderJob { RequestBody = requestBody });
}
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(PrerenderJob job)

8
extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs

@ -9,8 +9,6 @@ using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
@ -33,14 +31,12 @@ namespace Squidex.Extensions.Actions.Slack
protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action)
{
var body =
new JObject(
new JProperty("text", Format(action.Text, @event)));
var body = new { text = Format(action.Text, @event) };
var ruleJob = new SlackJob
{
RequestUrl = action.WebhookUrl.ToString(),
RequestBody = body.ToString(Formatting.Indented)
RequestBody = ToJson(body)
};
return (Description, ruleJob);

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs

@ -7,7 +7,7 @@
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs

@ -6,7 +6,7 @@
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Security;
using System;
using System.Collections.Generic;

4
src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents
{
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{
var resultValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value.Where(x => !x.Value.IsNull()))
foreach (var partitionValue in fieldValue.Value.Where(x => x.Value.Type != JsonValueType.Null))
{
resultValue[partitionValue.Key] = partitionValue.Value;
}

22
src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -7,21 +7,24 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class ContentFieldData : Dictionary<string, JToken>, IEquatable<ContentFieldData>
public sealed class ContentFieldData : Dictionary<string, IJsonValue>, IEquatable<ContentFieldData>
{
private static readonly JTokenEqualityComparer JTokenEqualityComparer = new JTokenEqualityComparer();
public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase)
{
}
public ContentFieldData AddValue(string key, JToken value)
public ContentFieldData AddValue(string key, object value)
{
return AddJsonValue(key, JsonValue.Create(value));
}
public ContentFieldData AddJsonValue(string key, IJsonValue value)
{
Guard.NotNullOrEmpty(key, nameof(key));
@ -30,11 +33,6 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
public ContentFieldData AddValue(JToken value)
{
return AddValue(InvariantPartitioning.Instance.Master.Key, value);
}
public override bool Equals(object obj)
{
return Equals(obj as ContentFieldData);
@ -42,12 +40,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public bool Equals(ContentFieldData other)
{
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other, EqualityComparer<string>.Default, JTokenEqualityComparer));
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
}
public override int GetHashCode()
{
return this.DictionaryHashCode(EqualityComparer<string>.Default, JTokenEqualityComparer);
return this.DictionaryHashCode();
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs

@ -7,7 +7,7 @@
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Rules.Json
{

5
src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Squidex.Domain.Apps.Core.Rules
@ -23,12 +22,12 @@ namespace Squidex.Domain.Apps.Core.Rules
public string ActionName { get; set; }
public string ActionData { get; set; }
public string Description { get; set; }
public Instant Created { get; set; }
public Instant Expires { get; set; }
public JObject ActionData { get; set; }
}
}

2
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs

@ -7,7 +7,7 @@
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{

19
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -7,13 +7,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
@ -41,11 +40,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return result;
}
private static void AppendText(JToken value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects)
private static void AppendText(IJsonValue value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects)
{
if (value?.Type == JTokenType.String)
if (value.Type == JsonValueType.String)
{
var text = ((JValue)value).ToString(CultureInfo.InvariantCulture);
var text = value.ToString();
if (text.Length <= maxFieldLength)
{
@ -57,18 +56,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
stringBuilder.Append(text);
}
}
else if (value?.Type == JTokenType.Array)
else if (value is JsonArray array)
{
foreach (var item in value)
foreach (var item in array)
{
AppendText(item, stringBuilder, maxFieldLength, separator, true);
}
}
else if (value?.Type == JTokenType.Object && allowObjects)
else if (value is JsonObject obj && allowObjects)
{
foreach (JProperty property in value)
foreach (var item in obj.Values)
{
AppendText(property.Value, stringBuilder, maxFieldLength, separator, true);
AppendText(item, stringBuilder, maxFieldLength, separator, true);
}
}
}

6
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs

@ -7,10 +7,10 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
}
var result = new Dictionary<string, JToken>();
var result = new Dictionary<string, IJsonValue>();
foreach (var fieldValue in content)
{
@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var language in languagePreferences)
{
if (fieldData.TryGetValue(language, out var value) && value != null)
if (fieldData.TryGetValue(language, out var value) && value.Type != JsonValueType.Null)
{
result[fieldValue.Key] = value;

17
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -8,13 +8,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable RECS0002 // Convert anonymous method to method group
@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
foreach (var value in data.Values)
{
if (value.IsNull())
if (value.Type == JsonValueType.Null)
{
continue;
}
@ -78,13 +77,13 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
foreach (var partition in data)
{
if (partition.Value is JArray array)
if (partition.Value is JsonArray array)
{
for (var i = 0; i < array.Count; i++)
{
var id = array[i].ToString();
array[i] = urlGenerator.GenerateUrl(id);
array[i] = JsonValue.Create(urlGenerator.GenerateUrl(id));
}
}
}
@ -273,16 +272,16 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var partition in data)
{
if (!(partition.Value is JArray jArray))
if (!(partition.Value is JsonArray array))
{
continue;
}
var newArray = new JArray();
var newArray = JsonValue.Array();
foreach (var item in jArray.OfType<JObject>())
foreach (var item in array.OfType<JsonObject>())
{
var newItem = new JObject();
var newItem = JsonValue.Object();
foreach (var kvp in item)
{

4
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs

@ -5,12 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public static class Value
{
public static readonly JToken Unset = JValue.CreateUndefined();
public static readonly IJsonValue Unset = JsonValue.Create("UNSET");
}
}

22
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -7,41 +7,41 @@
using System;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public delegate JToken ValueConverter(JToken value, IField field);
public delegate IJsonValue ValueConverter(IJsonValue value, IField field);
public static class ValueConverters
{
public static ValueConverter DecodeJson()
public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer)
{
return (value, field) =>
{
if (!value.IsNull() && field is IField<JsonFieldProperties>)
if (field is IField<JsonFieldProperties> && value is JsonScalar<string> s)
{
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value.ToString()));
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(s.Value));
return JToken.Parse(decoded);
return jsonSerializer.Deserialize<IJsonValue>(decoded);
}
return value;
};
}
public static ValueConverter EncodeJson()
public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer)
{
return (value, field) =>
{
if (!value.IsNull() && field is IField<JsonFieldProperties>)
if (value.Type != JsonValueType.Null && field is IField<JsonFieldProperties>)
{
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonSerializer.Serialize(value)));
return encoded;
return JsonValue.Create(encoded);
}
return value;
@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{
return (value, field) =>
{
if (value.IsNull())
if (value.Type == JsonValueType.Null)
{
return value;
}

11
src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs

@ -5,12 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent
{
@ -56,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant());
if (field.RawProperties.IsRequired || defaultValue.IsNull())
if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null)
{
return;
}
@ -65,13 +64,13 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value))
{
fieldData.AddValue(key, defaultValue);
fieldData.AddJsonValue(key, defaultValue);
}
}
private static bool ShouldApplyDefaultValue(IField field, JToken value)
private static bool ShouldApplyDefaultValue(IField field, IJsonValue value)
{
return value.IsNull() || (field is IField<StringFieldProperties> && value is JValue jValue && Equals(jValue.Value, string.Empty));
return value.Type == JsonValueType.Null || (field is IField<StringFieldProperties> && value is JsonScalar<string> s && string.IsNullOrEmpty(s.Value));
}
}
}

50
src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs

@ -6,14 +6,14 @@
// ==========================================================================
using System.Globalization;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent
{
public sealed class DefaultValueFactory : IFieldVisitor<JToken>
public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue>
{
private readonly Instant now;
@ -22,71 +22,71 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
this.now = now;
}
public static JToken CreateDefaultValue(IField field, Instant now)
public static IJsonValue CreateDefaultValue(IField field, Instant now)
{
Guard.NotNull(field, nameof(field));
return field.Accept(new DefaultValueFactory(now));
}
public JToken Visit(IArrayField field)
public IJsonValue Visit(IArrayField field)
{
return new JArray();
return JsonValue.Array();
}
public JToken Visit(IField<AssetsFieldProperties> field)
public IJsonValue Visit(IField<AssetsFieldProperties> field)
{
return new JArray();
return JsonValue.Array();
}
public JToken Visit(IField<BooleanFieldProperties> field)
public IJsonValue Visit(IField<BooleanFieldProperties> field)
{
return field.Properties.DefaultValue;
return JsonValue.Create(field.Properties.DefaultValue);
}
public JToken Visit(IField<GeolocationFieldProperties> field)
public IJsonValue Visit(IField<GeolocationFieldProperties> field)
{
return JValue.CreateNull();
return JsonValue.Null;
}
public JToken Visit(IField<JsonFieldProperties> field)
public IJsonValue Visit(IField<JsonFieldProperties> field)
{
return JValue.CreateNull();
return JsonValue.Object();
}
public JToken Visit(IField<NumberFieldProperties> field)
public IJsonValue Visit(IField<NumberFieldProperties> field)
{
return field.Properties.DefaultValue;
return JsonValue.Create(field.Properties.DefaultValue);
}
public JToken Visit(IField<ReferencesFieldProperties> field)
public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{
return new JArray();
return JsonValue.Array();
}
public JToken Visit(IField<StringFieldProperties> field)
public IJsonValue Visit(IField<StringFieldProperties> field)
{
return field.Properties.DefaultValue;
return JsonValue.Create(field.Properties.DefaultValue);
}
public JToken Visit(IField<TagsFieldProperties> field)
public IJsonValue Visit(IField<TagsFieldProperties> field)
{
return new JArray();
return JsonValue.Array();
}
public JToken Visit(IField<DateTimeFieldProperties> field)
public IJsonValue Visit(IField<DateTimeFieldProperties> field)
{
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now)
{
return now.ToString();
return JsonValue.Create(now.ToString());
}
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today)
{
return now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
return JsonValue.Create(now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
return field.Properties.DefaultValue?.ToString();
return JsonValue.Create(field.Properties.DefaultValue?.ToString());
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -11,7 +11,7 @@ using System.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
continue;
}
foreach (var partitionValue in fieldData.Where(x => !x.Value.IsNull()))
foreach (var partitionValue in fieldData.Where(x => x.Value.Type != JsonValueType.Null))
{
var ids = field.ExtractReferences(partitionValue.Value);

36
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -7,44 +7,44 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public sealed class ReferencesCleaner : IFieldVisitor<JToken>
public sealed class ReferencesCleaner : IFieldVisitor<IJsonValue>
{
private readonly JToken value;
private readonly IJsonValue value;
private readonly ICollection<Guid> oldReferences;
private ReferencesCleaner(JToken value, ICollection<Guid> oldReferences)
private ReferencesCleaner(IJsonValue value, ICollection<Guid> oldReferences)
{
this.value = value;
this.oldReferences = oldReferences;
}
public static JToken CleanReferences(IField field, JToken value, ICollection<Guid> oldReferences)
public static IJsonValue CleanReferences(IField field, IJsonValue value, ICollection<Guid> oldReferences)
{
return field.Accept(new ReferencesCleaner(value, oldReferences));
}
public JToken Visit(IField<AssetsFieldProperties> field)
public IJsonValue Visit(IField<AssetsFieldProperties> field)
{
return CleanIds();
}
public JToken Visit(IField<ReferencesFieldProperties> field)
public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{
if (oldReferences.Contains(field.Properties.SchemaId))
{
return new JArray();
return JsonValue.Array();
}
return CleanIds();
}
private JToken CleanIds()
private IJsonValue CleanIds()
{
var ids = value.ToGuidSet();
@ -55,45 +55,45 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
isRemoved |= ids.Remove(oldReference);
}
return isRemoved ? ids.ToJToken() : value;
return isRemoved ? ids.ToJsonArray() : value;
}
public JToken Visit(IField<BooleanFieldProperties> field)
public IJsonValue Visit(IField<BooleanFieldProperties> field)
{
return value;
}
public JToken Visit(IField<DateTimeFieldProperties> field)
public IJsonValue Visit(IField<DateTimeFieldProperties> field)
{
return value;
}
public JToken Visit(IField<GeolocationFieldProperties> field)
public IJsonValue Visit(IField<GeolocationFieldProperties> field)
{
return value;
}
public JToken Visit(IField<JsonFieldProperties> field)
public IJsonValue Visit(IField<JsonFieldProperties> field)
{
return value;
}
public JToken Visit(IField<NumberFieldProperties> field)
public IJsonValue Visit(IField<NumberFieldProperties> field)
{
return value;
}
public JToken Visit(IField<StringFieldProperties> field)
public IJsonValue Visit(IField<StringFieldProperties> field)
{
return value;
}
public JToken Visit(IField<TagsFieldProperties> field)
public IJsonValue Visit(IField<TagsFieldProperties> field)
{
return value;
}
public JToken Visit(IArrayField field)
public IJsonValue Visit(IArrayField field)
{
return value;
}

27
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -7,22 +7,21 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ReferencesExtensions
{
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value)
public static IEnumerable<Guid> ExtractReferences(this IField field, IJsonValue value)
{
return ReferencesExtractor.ExtractReferences(field, value);
}
public static JToken CleanReferences(this IField field, JToken value, ICollection<Guid> oldReferences)
public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection<Guid> oldReferences)
{
if (value.IsNull())
if (value.Type == JsonValueType.Null)
{
return value;
}
@ -30,31 +29,27 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return ReferencesCleaner.CleanReferences(field, value, oldReferences);
}
public static JToken ToJToken(this HashSet<Guid> ids)
public static JsonArray ToJsonArray(this HashSet<Guid> ids)
{
var result = new JArray();
var result = JsonValue.Array();
foreach (var id in ids)
{
result.Add(new JValue(id));
result.Add(JsonValue.Create(id.ToString()));
}
return result;
}
public static HashSet<Guid> ToGuidSet(this JToken value)
public static HashSet<Guid> ToGuidSet(this IJsonValue value)
{
if (value is JArray ids)
if (value is JsonArray array)
{
var result = new HashSet<Guid>();
foreach (var id in ids)
foreach (var id in array)
{
if (id.Type == JTokenType.Guid)
{
result.Add((Guid)id);
}
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid))
if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid))
{
result.Add(guid);
}

12
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -8,21 +8,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{
private readonly JToken value;
private readonly IJsonValue value;
private ReferencesExtractor(JToken value)
private ReferencesExtractor(IJsonValue value)
{
this.value = value;
}
public static IEnumerable<Guid> ExtractReferences(IField field, JToken value)
public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value)
{
return field.Accept(new ReferencesExtractor(value));
}
@ -31,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
var result = new List<Guid>();
if (value is JArray items)
if (value is JsonArray array)
{
foreach (JObject item in items)
foreach (JsonObject item in array)
{
foreach (var nestedField in field.Fields)
{

4
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs

@ -8,7 +8,7 @@
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return (value, field) =>
{
if (value.IsNull())
if (value.Type == JsonValueType.Null)
{
return value;
}

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

@ -6,7 +6,7 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Shared.Users;
@ -25,10 +25,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
public long Version { get; set; }
[JsonIgnore]
[IgnoreDataMember]
public abstract Guid AggregateId { get; }
[JsonIgnore]
[IgnoreDataMember]
public IUser User { get; set; }
}
}

7
src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs

@ -7,7 +7,6 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
@ -17,8 +16,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
Type ActionType { get; }
Task<(string Description, JObject Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Type DataType { get; }
Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data);
Task<(string Description, object Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Task<(string Dump, Exception Exception)> ExecuteJobAsync(object data);
}
}

28
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -7,7 +7,6 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
@ -25,29 +24,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TAction); }
}
protected RuleActionHandler(RuleEventFormatter formatter)
Type IRuleActionHandler.DataType
{
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
get { return typeof(TData); }
}
protected virtual string ToPayloadJson<T>(T @event)
protected RuleActionHandler(RuleEventFormatter formatter)
{
return formatter.ToPayload(@event).ToString();
}
Guard.NotNull(formatter, nameof(formatter));
protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event).ToString();
this.formatter = formatter;
}
protected virtual JObject ToPayload<T>(T @event)
protected virtual string ToJson<T>(T @event)
{
return formatter.ToPayload(@event);
}
protected virtual JObject ToEnvelope(EnrichedEvent @event)
protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event);
}
@ -62,16 +56,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return formatter.Format(text, @event);
}
async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
async Task<(string Description, object Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
{
var (description, data) = await CreateJobAsync(@event, (TAction)action);
return (description, JObject.FromObject(data));
return (description, data);
}
async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(JObject data)
async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(object data)
{
var typedData = data.ToObject<TData>();
var typedData = (TData)data;
return await ExecuteJobAsync(typedData);
}

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

@ -10,11 +10,11 @@ using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
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.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules
@ -27,15 +27,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
private static readonly Regex ContentDataPlaceholderNew = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled);
private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>();
private readonly JsonSerializer serializer;
private readonly IJsonSerializer jsonSerializer;
private readonly IRuleUrlGenerator urlGenerator;
public RuleEventFormatter(JsonSerializer serializer, IRuleUrlGenerator urlGenerator)
public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.serializer = serializer;
this.jsonSerializer = jsonSerializer;
this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId);
@ -55,17 +55,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
patterns.Add((placeholder.ToCharArray(), generator));
}
public virtual JObject ToPayload<T>(T @event)
public virtual string ToPayload<T>(T @event)
{
return JObject.FromObject(@event, serializer);
return jsonSerializer.Serialize(@event);
}
public virtual JObject ToEnvelope(EnrichedEvent @event)
public virtual string ToEnvelope(EnrichedEvent @event)
{
return new JObject(
new JProperty("type", @event.Name),
new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Timestamp.ToString()));
return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp });
}
public string Format(string text, EnrichedEvent @event)
@ -264,14 +261,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
for (var j = 2; j < path.Length; j++)
{
if (value is JObject obj && obj.TryGetValue(path[j], out value))
if (value is JsonObject obj && obj.TryGetValue(path[j], out value))
{
continue;
}
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count)
if (value is JsonArray array && int.TryParse(path[j], out var idx) && idx >= 0 && idx < array.Count)
{
value = arr[idx];
value = array[idx];
}
else
{
@ -279,17 +276,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
}
}
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined)
if (value == null || value.Type == JsonValueType.Null)
{
return Undefined;
}
if (value is JValue jValue)
{
return jValue.Value.ToString();
}
return value.ToString(Formatting.Indented) ?? Undefined;
return value.ToString() ?? Undefined;
}
}
}

21
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -11,12 +11,12 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.HandleRules
{
@ -26,15 +26,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers;
private readonly TypeNameRegistry typeNameRegistry;
private readonly IEventEnricher eventEnricher;
private readonly IJsonSerializer jsonSerializer;
private readonly IClock clock;
public RuleService(
IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers,
IEnumerable<IRuleActionHandler> ruleActionHandlers,
IEventEnricher eventEnricher,
IJsonSerializer jsonSerializer,
IClock clock,
TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers));
Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers));
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
@ -48,6 +51,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.eventEnricher = eventEnricher;
this.jsonSerializer = jsonSerializer;
this.clock = clock;
}
@ -88,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var now = clock.GetCurrentInstant();
var eventTime =
@event.Headers.Contains(CommonHeaders.Timestamp) ?
@event.Headers.ContainsKey(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() :
now;
@ -104,11 +109,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action);
var json = jsonSerializer.Serialize(actionData.Data);
var job = new RuleJob
{
JobId = Guid.NewGuid(),
ActionName = actionName,
ActionData = actionData.Data,
ActionData = json,
AggregateId = enrichedEvent.AggregateId,
AppId = appEvent.AppId.Id,
Created = now,
@ -120,14 +127,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return job;
}
public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, JObject job)
public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job)
{
try
{
var actionType = typeNameRegistry.GetType(actionName);
var actionWatch = Stopwatch.StartNew();
var result = await ruleActionHandlers[actionType].ExecuteJobAsync(job);
var actionHandler = ruleActionHandlers[actionType];
var deserialized = jsonSerializer.Deserialize<object>(job, actionHandler.DataType);
var result = await actionHandler.ExecuteJobAsync(deserialized);
actionWatch.Stop();

8
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -7,14 +7,14 @@
using Jint.Native;
using Jint.Runtime.Descriptors;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public sealed class ContentFieldProperty : PropertyDescriptor
{
private readonly ContentFieldObject contentField;
private JToken contentValue;
private IJsonValue contentValue;
private JsValue value;
private bool isChanged;
@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
}
}
public JToken ContentValue
public IJsonValue ContentValue
{
get { return contentValue ?? (contentValue = JsonMapper.Map(value)); }
}
@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
get { return isChanged; }
}
public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null)
public ContentFieldProperty(ContentFieldObject contentField, IJsonValue contentValue = null)
: base(null, true, true, true)
{
this.contentField = contentField;

94
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -9,61 +9,52 @@ using System;
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{
public static class JsonMapper
{
public static JsValue Map(JToken value, Engine engine)
public static JsValue Map(IJsonValue value, Engine engine)
{
if (value == null)
{
return JsValue.Null;
}
switch (value.Type)
switch (value)
{
case JTokenType.Date:
case JTokenType.Guid:
case JTokenType.String:
case JTokenType.Uri:
case JTokenType.TimeSpan:
return new JsValue((string)value);
case JTokenType.Null:
case JsonNull n:
return JsValue.Null;
case JTokenType.Undefined:
return JsValue.Undefined;
case JTokenType.Integer:
return new JsValue((long)value);
case JTokenType.Float:
return new JsValue((double)value);
case JTokenType.Boolean:
return new JsValue((bool)value);
case JTokenType.Object:
return FromObject(value, engine);
case JTokenType.Array:
{
var arr = (JArray)value;
var target = new JsValue[arr.Count];
for (var i = 0; i < arr.Count; i++)
{
target[i] = Map(arr[i], engine);
}
return engine.Array.Construct(target);
}
case JsonScalar<string> s:
return new JsValue(s.Value);
case JsonScalar<bool> b:
return new JsValue(b.Value);
case JsonScalar<double> b:
return new JsValue(b.Value);
case JsonObject obj:
return FromObject(obj, engine);
case JsonArray arr:
return FromArray(arr, engine);
}
throw new ArgumentException("Invalid json type.", nameof(value));
}
private static JsValue FromObject(JToken value, Engine engine)
private static JsValue FromArray(JsonArray arr, Engine engine)
{
var obj = (JObject)value;
var target = new JsValue[arr.Count];
for (var i = 0; i < arr.Count; i++)
{
target[i] = Map(arr[i], engine);
}
return engine.Array.Construct(target);
}
private static JsValue FromObject(JsonObject obj, Engine engine)
{
var target = new ObjectInstance(engine);
foreach (var property in obj)
@ -74,69 +65,64 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return target;
}
public static JToken Map(JsValue value)
public static IJsonValue Map(JsValue value)
{
if (value == null || value.IsNull())
{
return JValue.CreateNull();
}
if (value.IsUndefined())
if (value == null || value.IsNull() || value.IsUndefined())
{
return JValue.CreateUndefined();
return JsonValue.Null;
}
if (value.IsString())
{
return new JValue(value.AsString());
return JsonValue.Create(value.AsString());
}
if (value.IsBoolean())
{
return new JValue(value.AsBoolean());
return JsonValue.Create(value.AsBoolean());
}
if (value.IsNumber())
{
return new JValue(value.AsNumber());
return JsonValue.Create(value.AsNumber());
}
if (value.IsDate())
{
return new JValue(value.AsDate().ToDateTime());
return JsonValue.Create(value.AsDate().ToString());
}
if (value.IsRegExp())
{
return JValue.CreateString(value.AsRegExp().Value?.ToString());
return JsonValue.Create(value.AsRegExp().Value?.ToString());
}
if (value.IsArray())
{
var arr = value.AsArray();
var target = new JArray();
var result = JsonValue.Array();
for (var i = 0; i < arr.GetLength(); i++)
{
target.Add(Map(arr.Get(i.ToString())));
result.Add(Map(arr.Get(i.ToString())));
}
return target;
return result;
}
if (value.IsObject())
{
var obj = value.AsObject();
var target = new JObject();
var result = JsonValue.Object();
foreach (var kvp in obj.GetOwnProperties())
{
target[kvp.Key] = Map(kvp.Value.Value);
result[kvp.Key] = Map(kvp.Value.Value);
}
return target;
return result;
}
throw new ArgumentException("Invalid json type.", nameof(value));

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

@ -16,7 +16,6 @@
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NJsonSchema" Version="9.12.2" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />

34
src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs

@ -8,10 +8,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Tags
{
@ -24,10 +24,10 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(newData, nameof(newData));
var newValues = new HashSet<string>();
var newArrays = new List<JArray>();
var newArrays = new List<JsonArray>();
var oldValues = new HashSet<string>();
var oldArrays = new List<JArray>();
var oldArrays = new List<JsonArray>();
GetValues(schema, newValues, newArrays, newData);
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{
if (normalized.TryGetValue(array[i].ToString(), out var result))
{
array[i] = result;
array[i] = JsonValue.Create(result);
}
}
}
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(schema, nameof(schema));
var tagsValues = new HashSet<string>();
var tagsArrays = new List<JArray>();
var tagsArrays = new List<JsonArray>();
GetValues(schema, tagsValues, tagsArrays, datas);
@ -73,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Tags
{
if (denormalized.TryGetValue(array[i].ToString(), out var result))
{
array[i] = result;
array[i] = JsonValue.Create(result);
}
}
}
}
}
private static void GetValues(Schema schema, HashSet<string> values, List<JArray> arrays, params NamedContentData[] datas)
private static void GetValues(Schema schema, HashSet<string> values, List<JsonArray> arrays, params NamedContentData[] datas)
{
foreach (var field in schema.Fields)
{
@ -109,14 +109,12 @@ namespace Squidex.Domain.Apps.Core.Tags
{
foreach (var partition in fieldData)
{
if (partition.Value is JArray jArray)
if (partition.Value is JsonArray array)
{
foreach (var value in jArray)
foreach (var value in array)
{
if (value.Type == JTokenType.Object)
if (value is JsonObject nestedObject)
{
var nestedObject = (JObject)value;
if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue))
{
ExtractTags(nestedValue, values, arrays);
@ -133,19 +131,19 @@ namespace Squidex.Domain.Apps.Core.Tags
}
}
private static void ExtractTags(JToken token, ISet<string> values, ICollection<JArray> arrays)
private static void ExtractTags(IJsonValue value, ISet<string> values, ICollection<JsonArray> arrays)
{
if (token is JArray jArray)
if (value is JsonArray array)
{
foreach (var value in jArray)
foreach (var item in array)
{
if (value.Type == JTokenType.String)
if (item.Type == JsonValueType.String)
{
values.Add(value.ToString());
values.Add(item.ToString());
}
}
arrays.Add(jArray);
arrays.Add(array);
}
}
}

5
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -9,11 +9,11 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace
@ -22,7 +22,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public sealed class ContentValidator
{
private static readonly ContentFieldData DefaultFieldData = new ContentFieldData();
private static readonly JToken DefaultValue = JValue.CreateNull();
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context;
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var type = isLanguage ? "language" : "invariant value";
return new ObjectValidator<JToken>(fieldsValidators, isPartial, type, DefaultValue);
return new ObjectValidator<IJsonValue>(fieldsValidators, isPartial, type, JsonValue.Null);
}
}
}

175
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -7,45 +7,80 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class JsonValueConverter : IFieldVisitor<object>
{
private readonly JToken value;
private readonly IJsonValue value;
private JsonValueConverter(JToken value)
private JsonValueConverter(IJsonValue value)
{
this.value = value;
}
public static object ConvertValue(IField field, JToken json)
public static object ConvertValue(IField field, IJsonValue json)
{
return field.Accept(new JsonValueConverter(json));
}
public object Visit(IArrayField field)
{
return value.ToObject<List<JObject>>();
return ConvertToObjectList();
}
public object Visit(IField<AssetsFieldProperties> field)
{
return value.ToObject<List<Guid>>();
return ConvertToGuidList();
}
public object Visit(IField<ReferencesFieldProperties> field)
{
return ConvertToGuidList();
}
public object Visit(IField<TagsFieldProperties> field)
{
return ConvertToStringList();
}
public object Visit(IField<BooleanFieldProperties> field)
{
return (bool?)value;
if (value is JsonScalar<bool> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected boolean.");
}
public object Visit(IField<NumberFieldProperties> field)
{
if (value is JsonScalar<double> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected number.");
}
public object Visit(IField<StringFieldProperties> field)
{
if (value is JsonScalar<string> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected string.");
}
public object Visit(IField<DateTimeFieldProperties> field)
{
if (value.Type == JTokenType.String)
if (value.Type == JsonValueType.String)
{
var parseResult = InstantPattern.General.Parse(value.ToString());
@ -62,31 +97,49 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IField<GeolocationFieldProperties> field)
{
var geolocation = (JObject)value;
foreach (var property in geolocation.Properties())
if (value is JsonObject geolocation)
{
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
foreach (var propertyName in geolocation.Keys)
{
throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
}
}
}
var lat = (double)geolocation["latitude"];
var lon = (double)geolocation["longitude"];
if (geolocation.TryGetValue("latitude", out var latValue) && latValue is JsonScalar<double> latNumber)
{
var lat = latNumber.Value;
if (!lat.IsBetween(-90, 90))
{
throw new InvalidCastException("Latitude must be between -90 and 90.");
}
if (!lat.IsBetween(-90, 90))
{
throw new InvalidCastException("Latitude must be between -90 and 90.");
}
}
else
{
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
if (!lon.IsBetween(-180, 180))
{
throw new InvalidCastException("Longitude must be between -180 and 180.");
if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonScalar<double> lonNumber)
{
var lon = lonNumber.Value;
if (!lon.IsBetween(-180, 180))
{
throw new InvalidCastException("Longitude must be between -180 and 180.");
}
}
else
{
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
return value;
}
return value;
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
public object Visit(IField<JsonFieldProperties> field)
@ -94,24 +147,76 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return value;
}
public object Visit(IField<NumberFieldProperties> field)
private object ConvertToGuidList()
{
return (double?)value;
}
if (value is JsonArray array)
{
var result = new List<Guid>();
public object Visit(IField<ReferencesFieldProperties> field)
{
return value.ToObject<List<Guid>>();
foreach (var item in array)
{
if (item is JsonScalar<string> s && Guid.TryParse(s.Value, out var guid))
{
result.Add(guid);
}
else
{
throw new InvalidCastException("Invalid json type, expected array of guid strings.");
}
}
return result;
}
throw new InvalidCastException("Invalid json type, expected array of guid strings.");
}
public object Visit(IField<StringFieldProperties> field)
private object ConvertToStringList()
{
return value.ToString();
if (value is JsonArray array)
{
var result = new List<string>();
foreach (var item in array)
{
if (item is JsonScalar<string> s)
{
result.Add(s.Value);
}
else
{
throw new InvalidCastException("Invalid json type, expected array of strings.");
}
}
return result;
}
throw new InvalidCastException("Invalid json type, expected array of strings.");
}
public object Visit(IField<TagsFieldProperties> field)
private object ConvertToObjectList()
{
return value.ToObject<List<string>>();
if (value is JsonArray array)
{
var result = new List<JsonObject>();
foreach (var item in array)
{
if (item is JsonObject obj)
{
result.Add(obj);
}
else
{
throw new InvalidCastException("Invalid json type, expected array of objects.");
}
}
return result;
}
throw new InvalidCastException("Invalid json type, expected array of objects.");
}
}
}

7
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -7,9 +7,8 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
object typedValue = null;
if (value is JToken jToken)
if (value is IJsonValue jsonValue)
{
typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken);
typedValue = jsonValue.Type == JsonValueType.Null ? null : JsonValueConverter.ConvertValue(field, jsonValue);
}
var tasks = new List<Task>();

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs

@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
if (value is IDictionary<string, TValue> values)
if (value is IReadOnlyDictionary<string, TValue> values)
{
foreach (var fieldData in values)
{

4
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs

@ -8,11 +8,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField));
}
yield return new CollectionItemValidator(new ObjectValidator<JToken>(nestedSchema, false, "field", JValue.CreateNull()));
yield return new CollectionItemValidator(new ObjectValidator<IJsonValue>(nestedSchema, false, "field", JsonValue.Null));
}
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field)

13
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs

@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
@ -22,24 +23,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return data.GetReferencedIds(schema).ToList();
}
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds)
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds, IJsonSerializer serializer)
{
return result.ConvertId2Name(schema,
FieldConverters.ForValues(
ValueConverters.DecodeJson(),
ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)),
FieldConverters.ForNestedId2Name(
ValueConverters.DecodeJson(),
ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)));
}
public static IdContentData ToMongoModel(this NamedContentData result, Schema schema)
public static IdContentData ToMongoModel(this NamedContentData result, Schema schema, IJsonSerializer serializer)
{
return result.ConvertName2Id(schema,
FieldConverters.ForValues(
ValueConverters.EncodeJson()),
ValueConverters.EncodeJson(serializer)),
FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson()));
ValueConverters.EncodeJson(serializer)));
}
}
}

11
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
@ -26,10 +27,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
private readonly string collectionName;
public MongoContentCollection(IMongoDatabase database, string collectionName)
protected IJsonSerializer Serializer { get; }
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName)
: base(database)
{
this.collectionName = collectionName;
Serializer = serializer;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef);
entity.ParseData(schema.SchemaDef, Serializer);
}
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
@ -96,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef);
entity.ParseData(schema.SchemaDef, Serializer);
}
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);

9
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs

@ -19,6 +19,7 @@ using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
@ -28,8 +29,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal sealed class MongoContentDraftCollection : MongoContentCollection
{
public MongoContentDraftCollection(IMongoDatabase database)
: base(database, "State_Content_Draft")
public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, serializer, "State_Content_Draft")
{
}
@ -92,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef);
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}
@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef);
contentEntity.ParseData(schema.SchemaDef, Serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}

7
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
@ -124,13 +125,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
get { return dataDraft; }
}
public void ParseData(Schema schema)
public void ParseData(Schema schema, IJsonSerializer serializer)
{
data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted);
data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted, serializer);
if (DataDraftByIds != null)
{
dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted);
dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted, serializer);
}
}
}

7
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs

@ -13,14 +13,15 @@ using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal sealed class MongoContentPublishedCollection : MongoContentCollection
{
public MongoContentPublishedCollection(IMongoDatabase database)
: base(database, "State_Content_Published")
public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, serializer, "State_Content_Published")
{
}
@ -42,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef);
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}

11
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries;
@ -26,17 +27,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
private readonly IMongoDatabase database;
private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
private readonly MongoContentDraftCollection contentsDraft;
private readonly MongoContentPublishedCollection contentsPublished;
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider;
contentsDraft = new MongoContentDraftCollection(database);
contentsPublished = new MongoContentPublishedCollection(database);
this.serializer = serializer;
contentsDraft = new MongoContentDraftCollection(database, serializer);
contentsPublished = new MongoContentPublishedCollection(database, serializer);
this.database = database;
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -38,12 +38,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id);
var idData = value.Data.ToMongoModel(schema.SchemaDef);
var idData = value.Data.ToMongoModel(schema.SchemaDef, serializer);
var idDraftData = idData;
if (!ReferenceEquals(value.Data, value.DataDraft))
{
idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef);
idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef, serializer);
}
var content = SimpleMapper.Map(value, new MongoContentEntity

18
src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -8,8 +8,8 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
[CollectionName("UISettings")]
public sealed class State
{
public JObject Settings { get; set; } = new JObject();
public JsonObject Settings { get; set; } = JsonValue.Object();
}
public AppUISettingsGrain(IStore<Guid> store)
@ -41,19 +41,19 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.ReadAsync();
}
public Task<J<JObject>> GetAsync()
public Task<J<JsonObject>> GetAsync()
{
return Task.FromResult(state.Settings.AsJ());
}
public Task SetAsync(J<JObject> settings)
public Task SetAsync(J<JsonObject> settings)
{
state.Settings = settings;
return persistence.WriteSnapshotAsync(state);
}
public Task SetAsync(string path, J<JToken> value)
public Task SetAsync(string path, J<IJsonValue> value)
{
var container = GetContainer(path, out var key);
@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
throw new InvalidOperationException("Path does not lead to an object.");
}
container[key] = value;
container[key] = value.Value;
return persistence.WriteSnapshotAsync(state);
}
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.WriteSnapshotAsync(state);
}
private JObject GetContainer(string path, out string key)
private JsonObject GetContainer(string path, out string key)
{
Guard.NotNullOrEmpty(path, nameof(path));
@ -95,12 +95,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
if (!current.TryGetValue(segment, out var temp))
{
temp = new JObject();
temp = JsonValue.Object();
current[segment] = temp;
}
if (temp is JObject next)
if (temp is JsonObject next)
{
current = next;
}

19
src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup;
@ -16,6 +15,8 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Squidex.Shared.Users;
@ -27,6 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private const string SettingsFile = "Settings.json";
private readonly IGrainFactory grainFactory;
private readonly IUserResolver userResolver;
private readonly IJsonSerializer serializer;
private readonly IAppsByNameIndex appsByNameIndex;
private readonly HashSet<string> contributors = new HashSet<string>();
private Dictionary<string, string> usersWithEmail = new Dictionary<string, string>();
@ -36,13 +38,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
public override string Name { get; } = "Apps";
public BackupApps(IGrainFactory grainFactory, IUserResolver userResolver)
public BackupApps(IGrainFactory grainFactory, IUserResolver userResolver, IJsonSerializer serializer)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(userResolver, nameof(userResolver));
this.grainFactory = grainFactory;
this.serializer = serializer;
this.userResolver = userResolver;
appsByNameIndex = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
@ -162,14 +165,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadUsersAsync(BackupReader reader)
{
var json = await reader.ReadJsonAttachmentAsync(UsersFile);
var json = await reader.ReadJsonAttachmentAsync<Dictionary<string, string>>(UsersFile);
usersWithEmail = json.ToObject<Dictionary<string, string>>();
usersWithEmail = json;
}
private async Task WriteUsersAsync(BackupWriter writer)
{
var json = JObject.FromObject(usersWithEmail);
var json = usersWithEmail;
await writer.WriteJsonAsync(UsersFile, json);
}
@ -183,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadSettingsAsync(BackupReader reader, Guid appId)
{
var json = await reader.ReadJsonAttachmentAsync(SettingsFile);
var json = await reader.ReadJsonAttachmentAsync<JsonObject>(SettingsFile);
await grainFactory.GetGrain<IAppUISettingsGrain>(appId).SetAsync((JObject)json);
await grainFactory.GetGrain<IAppUISettingsGrain>(appId).SetAsync(json);
}
public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader)

8
src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs

@ -6,19 +6,19 @@
// ==========================================================================
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppUISettingsGrain : IGrainWithGuidKey
{
Task<J<JObject>> GetAsync();
Task<J<JsonObject>> GetAsync();
Task SetAsync(string path, J<JToken> value);
Task SetAsync(string path, J<IJsonValue> value);
Task SetAsync(J<JObject> settings);
Task SetAsync(J<JsonObject> settings);
Task RemoveAsync(string path);
}

18
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
[CollectionName("Apps")]
public class AppState : DomainObjectState<AppState>, IAppEntity
{
[JsonProperty]
[DataMember]
public string Name { get; set; }
[JsonProperty]
[DataMember]
public Roles Roles { get; set; } = Roles.Empty;
[JsonProperty]
[DataMember]
public AppPlan Plan { get; set; }
[JsonProperty]
[DataMember]
public AppClients Clients { get; set; } = AppClients.Empty;
[JsonProperty]
[DataMember]
public AppPatterns Patterns { get; set; } = AppPatterns.Empty;
[JsonProperty]
[DataMember]
public AppContributors Contributors { get; set; } = AppContributors.Empty;
[JsonProperty]
[DataMember]
public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English;
[JsonProperty]
[DataMember]
public bool IsArchived { get; set; }
protected void On(AppCreated @event)

7
src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
@ -89,16 +88,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
private async Task RestoreTagsAsync(Guid appId, BackupReader reader)
{
var tags = await reader.ReadJsonAttachmentAsync(TagsFile);
var tags = await reader.ReadJsonAttachmentAsync<TagSet>(TagsFile);
await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags.ToObject<TagSet>());
await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags);
}
private async Task BackupTagsAsync(Guid appId, BackupWriter writer)
{
var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets);
await writer.WriteJsonAsync(TagsFile, JObject.FromObject(tags));
await writer.WriteJsonAsync(TagsFile, tags);
}
private Task WriteAssetAsync(Guid assetId, long fileVersion, BackupWriter writer)

24
src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
@ -20,37 +20,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{
public class AssetState : DomainObjectState<AssetState>, IAssetEntity
{
[JsonProperty]
[DataMember]
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
[DataMember]
public string FileName { get; set; }
[JsonProperty]
[DataMember]
public string MimeType { get; set; }
[JsonProperty]
[DataMember]
public long FileVersion { get; set; }
[JsonProperty]
[DataMember]
public long FileSize { get; set; }
[JsonProperty]
[DataMember]
public long TotalSize { get; set; }
[JsonProperty]
[DataMember]
public bool IsImage { get; set; }
[JsonProperty]
[DataMember]
public int? PixelWidth { get; set; }
[JsonProperty]
[DataMember]
public int? PixelHeight { get; set; }
[JsonProperty]
[DataMember]
public bool IsDeleted { get; set; }
[JsonProperty]
[DataMember]
public HashSet<string> Tags { get; set; }
Guid IAssetInfo.AssetId

7
src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
@ -34,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log,
IStore<Guid> store)
{
@ -60,6 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store));
Guard.NotNull(log, nameof(log));
@ -69,6 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers;
this.serializer = serializer;
this.store = store;
this.log = log;
}
@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id))
{
using (var writer = new BackupWriter(stream, true))
using (var writer = new BackupWriter(serializer, stream, true))
{
await eventStore.QueryAsync(async storedEvent =>
{

72
src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -8,21 +8,25 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Helpers;
using Squidex.Domain.Apps.Entities.Backup.Model;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.States;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupReader : DisposableObjectBase
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly GuidMapper guidMapper = new GuidMapper();
private readonly ZipArchive archive;
private readonly IJsonSerializer serializer;
private int readEvents;
private int readAttachments;
@ -36,8 +40,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
get { return readAttachments; }
}
public BackupReader(Stream stream)
public BackupReader(IJsonSerializer serializer, Stream stream)
{
Guard.NotNull(serializer, nameof(serializer));
this.serializer = serializer;
archive = new ZipArchive(stream, ZipArchiveMode.Read, false);
}
@ -54,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
return guidMapper.OldGuid(newId);
}
public async Task<JToken> ReadJsonAttachmentAsync(string name)
public Task<T> ReadJsonAttachmentAsync<T>(string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
@ -65,24 +73,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
throw new FileNotFoundException("Cannot find attachment.", name);
}
JToken result;
T result;
using (var stream = attachmentEntry.Open())
{
using (var textReader = new StreamReader(stream))
{
using (var jsonReader = new JsonTextReader(textReader))
{
result = await JToken.ReadFromAsync(jsonReader);
guidMapper.NewGuids(result);
}
}
result = serializer.Deserialize<T>(stream, null, guidMapper.NewGuidOrValue);
}
readAttachments++;
return result;
return Task.FromResult(result);
}
public async Task ReadBlobAsync(string name, Func<Stream, Task> handler)
@ -105,9 +105,10 @@ namespace Squidex.Domain.Apps.Entities.Backup
readAttachments++;
}
public async Task ReadEventsAsync(IStreamNameResolver streamNameResolver, Func<StoredEvent, Task> handler)
public async Task ReadEventsAsync(IStreamNameResolver streamNameResolver, IEventDataFormatter formatter, Func<(string Stream, Envelope<IEvent> Event), Task> handler)
{
Guard.NotNull(handler, nameof(handler));
Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
while (true)
@ -121,29 +122,34 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
using (var textReader = new StreamReader(stream))
{
using (var jsonReader = new JsonTextReader(textReader))
{
var storedEvent = Serializer.Deserialize<StoredEvent>(jsonReader);
var (streamName, data) = serializer.Deserialize<CompatibleStoredEvent>(stream).ToEvent();
storedEvent.Data.Payload = guidMapper.NewGuids(storedEvent.Data.Payload);
storedEvent.Data.Metadata = guidMapper.NewGuids(storedEvent.Data.Metadata);
MapHeaders(data);
var streamName = streamNameResolver.WithNewId(storedEvent.StreamName, guidMapper.NewGuidString);
var eventStream = streamNameResolver.WithNewId(streamName, guidMapper.NewGuidOrNull);
var eventEnvelope = formatter.Parse(data, guidMapper.NewGuidOrValue);
storedEvent = new StoredEvent(streamName,
storedEvent.EventPosition,
storedEvent.EventStreamNumber,
storedEvent.Data);
await handler(storedEvent);
}
}
await handler((eventStream, eventEnvelope));
}
readEvents++;
}
}
private void MapHeaders(EventData data)
{
foreach (var kvp in data.Headers.ToList())
{
if (kvp.Value.Type == JsonValueType.String)
{
var newGuid = guidMapper.NewGuidOrNull(kvp.Value.ToString());
if (newGuid != null)
{
data.Headers.Add(kvp.Key, newGuid);
}
}
}
}
}
}

15
src/Squidex.Domain.Apps.Entities/Backup/BackupVersion.cs

@ -0,0 +1,15 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Backup
{
public enum BackupVersion
{
V2,
V1
}
}

41
src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs

@ -9,18 +9,20 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Helpers;
using Squidex.Domain.Apps.Entities.Backup.Model;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class BackupWriter : DisposableObjectBase
{
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly ZipArchive archive;
private readonly IJsonSerializer serializer;
private readonly Func<StoredEvent, CompatibleStoredEvent> converter;
private int writtenEvents;
private int writtenAttachments;
@ -34,8 +36,17 @@ namespace Squidex.Domain.Apps.Entities.Backup
get { return writtenAttachments; }
}
public BackupWriter(Stream stream, bool keepOpen = false)
public BackupWriter(IJsonSerializer serializer, Stream stream, bool keepOpen = false, BackupVersion version = BackupVersion.V2)
{
Guard.NotNull(serializer, nameof(serializer));
this.serializer = serializer;
converter =
version == BackupVersion.V1 ?
new Func<StoredEvent, CompatibleStoredEvent>(CompatibleStoredEvent.V1) :
new Func<StoredEvent, CompatibleStoredEvent>(CompatibleStoredEvent.V2);
archive = new ZipArchive(stream, ZipArchiveMode.Create, keepOpen);
}
@ -47,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
public async Task WriteJsonAsync(string name, JToken value)
public Task WriteJsonAsync(string name, object value)
{
Guard.NotNullOrEmpty(name, nameof(name));
@ -55,16 +66,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = attachmentEntry.Open())
{
using (var textWriter = new StreamWriter(stream))
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
await value.WriteToAsync(jsonWriter);
}
}
serializer.Serialize(value, stream);
}
writtenAttachments++;
return TaskHelper.Done;
}
public async Task WriteBlobAsync(string name, Func<Stream, Task> handler)
@ -90,13 +97,9 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open())
{
using (var textWriter = new StreamWriter(stream))
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
Serializer.Serialize(jsonWriter, storedEvent);
}
}
var @event = converter(storedEvent);
serializer.Serialize(@event, stream);
}
writtenEvents++;

118
src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs

@ -7,149 +7,87 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Backup
{
public sealed class GuidMapper
internal sealed class GuidMapper
{
private static readonly int GuidLength = Guid.Empty.ToString().Length;
private readonly List<(JObject Source, string NewKey, string OldKey)> mappings = new List<(JObject Source, string NewKey, string OldKey)>();
private readonly Dictionary<Guid, Guid> oldToNewGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<Guid, Guid> newToOldGuid = new Dictionary<Guid, Guid>();
public Guid NewGuid(Guid oldGuid)
{
return oldToNewGuid.GetOrDefault(oldGuid);
}
private readonly Dictionary<string, string> strings = new Dictionary<string, string>();
public Guid OldGuid(Guid newGuid)
{
return newToOldGuid.GetOrDefault(newGuid);
return newToOldGuid.GetOrCreate(newGuid, x => x);
}
public string NewGuidString(string key)
public string NewGuidOrNull(string value)
{
if (Guid.TryParse(key, out var guid))
if (TryGenerateNewGuidString(value, out var result) || TryGenerateNewNamedId(value, out result))
{
return GenerateNewGuid(guid).ToString();
return result;
}
return null;
}
public JToken NewGuids(JToken jToken)
{
var result = NewGuidsCore(jToken);
if (mappings.Count > 0)
{
foreach (var mapping in mappings)
{
if (mapping.Source.TryGetValue(mapping.OldKey, out var value))
{
mapping.Source.Remove(mapping.OldKey);
mapping.Source[mapping.NewKey] = value;
}
}
mappings.Clear();
}
return result;
}
private JToken NewGuidsCore(JToken jToken)
public string NewGuidOrValue(string value)
{
switch (jToken.Type)
if (TryGenerateNewGuidString(value, out var result) || TryGenerateNewNamedId(value, out result))
{
case JTokenType.String:
if (TryConvertString(jToken.ToString(), out var result))
{
return result;
}
break;
case JTokenType.Guid:
return GenerateNewGuid((Guid)jToken);
case JTokenType.Object:
NewGuidsCore((JObject)jToken);
break;
case JTokenType.Array:
NewGuidsCore((JArray)jToken);
break;
return result;
}
return jToken;
}
private void NewGuidsCore(JArray jArray)
{
for (var i = 0; i < jArray.Count; i++)
{
jArray[i] = NewGuidsCore(jArray[i]);
}
return value;
}
private void NewGuidsCore(JObject jObject)
private bool TryGenerateNewGuidString(string value, out string result)
{
foreach (var jProperty in jObject.Properties())
if (value.Length == GuidLength)
{
var newValue = NewGuidsCore(jProperty.Value);
if (!ReferenceEquals(newValue, jProperty.Value))
{
jProperty.Value = newValue;
}
if (TryConvertString(jProperty.Name, out var newKey))
if (strings.TryGetValue(value, out result))
{
mappings.Add((jObject, newKey, jProperty.Name));
return true;
}
}
}
private bool TryConvertString(string value, out string result)
{
return TryGenerateNewGuidString(value, out result) || TryGenerateNewNamedId(value, out result);
}
private bool TryGenerateNewGuidString(string value, out string result)
{
result = null;
if (value.Length == GuidLength)
{
if (Guid.TryParse(value, out var guid))
{
var newGuid = GenerateNewGuid(guid);
result = newGuid.ToString();
strings[value] = result = newGuid.ToString();
return true;
}
}
result = null;
return false;
}
private bool TryGenerateNewNamedId(string value, out string result)
{
result = null;
if (value.Length > GuidLength && value[GuidLength] == ',')
if (value.Length > GuidLength)
{
if (Guid.TryParse(value.Substring(0, GuidLength), out var guid))
if (strings.TryGetValue(value, out result))
{
var newGuid = GenerateNewGuid(guid);
return true;
}
if (NamedId<Guid>.TryParse(value, Guid.TryParse, out var namedId))
{
var newGuid = GenerateNewGuid(namedId.Id);
result = newGuid + value.Substring(GuidLength);
strings[value] = result = new NamedId<Guid>(newGuid, namedId.Name).ToString();
return true;
}
}
result = null;
return false;
}

5
src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs

@ -9,6 +9,7 @@ using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Backup.Helpers
{
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers
}
}
public static async Task<BackupReader> OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, Guid id)
public static async Task<BackupReader> OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, Guid id, IJsonSerializer serializer)
{
Stream stream = null;
@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers
{
stream = await backupArchiveLocation.OpenStreamAsync(id);
return new BackupReader(stream);
return new BackupReader(serializer, stream);
}
catch (IOException)
{

116
src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs

@ -0,0 +1,116 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.EventSourcing;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Domain.Apps.Entities.Backup.Model
{
public sealed class CompatibleStoredEvent
{
[JsonProperty("n")]
public NewEvent NewEvent;
[JsonProperty]
public string StreamName;
[JsonProperty]
public string EventPosition;
[JsonProperty]
public long EventStreamNumber;
[JsonProperty]
public CompatibleEventData Data;
public static CompatibleStoredEvent V1(StoredEvent stored)
{
return new CompatibleStoredEvent
{
Data = CompatibleEventData.V1(stored.Data),
EventPosition = stored.EventPosition,
EventStreamNumber = stored.EventStreamNumber,
StreamName = stored.StreamName
};
}
public static CompatibleStoredEvent V2(StoredEvent stored)
{
return new CompatibleStoredEvent { NewEvent = NewEvent.V2(stored) };
}
public (string Stream, EventData Data) ToEvent()
{
if (NewEvent != null)
{
return NewEvent.ToEvent();
}
else
{
return (StreamName, Data.ToData());
}
}
}
public sealed class CompatibleEventData
{
[JsonProperty]
public string Type;
[JsonProperty]
public JRaw Payload;
[JsonProperty]
public EnvelopeHeaders Metadata;
public static CompatibleEventData V1(EventData data)
{
var payload = new JRaw(data.Payload);
return new CompatibleEventData { Type = data.Type, Payload = payload, Metadata = data.Headers };
}
public EventData ToData()
{
return new EventData(Type, Metadata, Payload.ToString());
}
}
public sealed class NewEvent
{
[JsonProperty("t")]
public string EventType;
[JsonProperty("s")]
public string StreamName;
[JsonProperty("p")]
public string EventPayload;
[JsonProperty("h")]
public EnvelopeHeaders EventHeaders;
public static NewEvent V2(StoredEvent stored)
{
return new NewEvent
{
EventType = stored.Data.Type,
EventHeaders = stored.Data.Headers,
EventPayload = stored.Data.Payload,
StreamName = stored.StreamName
};
}
public (string Stream, EventData Data) ToEvent()
{
return (StreamName, new EventData(EventType, EventHeaders, EventPayload));
}
}
}

23
src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
@ -31,6 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IClock clock;
private readonly ICommandBus commandBus;
private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter;
private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log,
IStreamNameResolver streamNameResolver,
IStore<string> store)
@ -61,6 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
Guard.NotNull(log, nameof(log));
@ -71,6 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers;
this.serializer = serializer;
this.store = store;
this.streamNameResolver = streamNameResolver;
this.log = log;
@ -161,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
await DownloadAsync();
}
using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id))
using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id, serializer))
{
using (Profiler.Trace("ReadEvents"))
{
@ -189,6 +194,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
}
}
await AssignContributorAsync();
CurrentJob.Status = JobStatus.Completed;
Log("Completed, Yeah!");
@ -247,6 +254,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
FromRestore = true,
Role = Role.Owner
});
Log("Assigned current user.");
}
private async Task CleanupAsync()
@ -273,17 +282,15 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ReadEventsAsync(BackupReader reader)
{
await reader.ReadEventsAsync(streamNameResolver, async storedEvent =>
await reader.ReadEventsAsync(streamNameResolver, eventDataFormatter, async storedEvent =>
{
var @event = eventDataFormatter.Parse(storedEvent.Data);
await HandleEventAsync(reader, storedEvent, @event);
await HandleEventAsync(reader, storedEvent.Stream, storedEvent.Event);
});
Log("Reading events completed.");
Log($"Reading {reader.ReadEvents} events and {reader.ReadAttachments} attachments completed.", true);
}
private async Task HandleEventAsync(BackupReader reader, StoredEvent storedEvent, Envelope<IEvent> @event)
private async Task HandleEventAsync(BackupReader reader, string stream, Envelope<IEvent> @event)
{
if (@event.Payload is SquidexEvent squidexEvent)
{
@ -316,7 +323,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
var eventData = eventDataFormatter.ToEventData(@event, @event.Headers.CommitId());
var eventCommit = new List<EventData> { eventData };
await eventStore.AppendAsync(Guid.NewGuid(), storedEvent.StreamName, eventCommit);
await eventStore.AppendAsync(Guid.NewGuid(), stream, eventCommit);
Log($"Read {reader.ReadEvents} events and {reader.ReadAttachments} attachments.", true);
}

4
src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs

@ -6,13 +6,13 @@
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Runtime.Serialization;
namespace Squidex.Domain.Apps.Entities.Backup.State
{
public sealed class BackupState
{
[JsonProperty]
[DataMember]
public List<BackupStateJob> Jobs { get; } = new List<BackupStateJob>();
}
}

14
src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs

@ -6,29 +6,29 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using NodaTime;
namespace Squidex.Domain.Apps.Entities.Backup.State
{
public sealed class BackupStateJob : IBackupJob
{
[JsonProperty]
[DataMember]
public Guid Id { get; set; }
[JsonProperty]
[DataMember]
public Instant Started { get; set; }
[JsonProperty]
[DataMember]
public Instant? Stopped { get; set; }
[JsonProperty]
[DataMember]
public int HandledEvents { get; set; }
[JsonProperty]
[DataMember]
public int HandledAssets { get; set; }
[JsonProperty]
[DataMember]
public JobStatus Status { get; set; }
}
}

4
src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using System.Runtime.Serialization;
namespace Squidex.Domain.Apps.Entities.Backup.State
{
public class RestoreState
{
[JsonProperty]
[DataMember]
public RestoreStateJob Job { get; set; }
}
}

21
src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs

@ -7,38 +7,39 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using NodaTime;
namespace Squidex.Domain.Apps.Entities.Backup.State
{
[DataContract]
public sealed class RestoreStateJob : IRestoreJob
{
[JsonProperty]
[DataMember]
public string AppName { get; set; }
[JsonProperty]
[DataMember]
public Guid Id { get; set; }
[JsonProperty]
[DataMember]
public Guid AppId { get; set; }
[JsonProperty]
[DataMember]
public Uri Url { get; set; }
[JsonProperty]
[DataMember]
public string NewAppName { get; set; }
[JsonProperty]
[DataMember]
public Instant Started { get; set; }
[JsonProperty]
[DataMember]
public Instant? Stopped { get; set; }
[JsonProperty]
[DataMember]
public List<string> Log { get; set; } = new List<string>();
[JsonProperty]
[DataMember]
public JobStatus Status { get; set; }
}
}

13
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -8,8 +8,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public sealed class GraphQLExecutionContext : QueryExecutionContext
@ -25,29 +26,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
UrlGenerator = urlGenerator;
}
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(JToken value)
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(IJsonValue value)
{
var ids = ParseIds(value);
return GetReferencedAssetsAsync(ids);
}
public Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, JToken value)
public Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, IJsonValue value)
{
var ids = ParseIds(value);
return GetReferencedContentsAsync(schemaId, ids);
}
private static ICollection<Guid> ParseIds(JToken value)
private static ICollection<Guid> ParseIds(IJsonValue value)
{
try
{
var result = new List<Guid>();
if (value is JArray)
if (value is JsonArray array)
{
foreach (var id in value)
foreach (var id in array)
{
result.Add(Guid.Parse(id.ToString()));
}

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public static readonly IGraphType Guid = new GuidGraphType2();
public static readonly IGraphType Date = new DateTimeGraphType();
public static readonly IGraphType Date = new InstantGraphType();
public static readonly IGraphType Json = new JsonGraphType();

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
Name = "created",
ResolvedType = AllTypes.NonNullDate,
Resolver = Resolve(x => x.Created.ToDateTimeUtc()),
Resolver = Resolve(x => x.Created),
Description = "The date and time when the asset has been created."
});
@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
Name = "lastModified",
ResolvedType = AllTypes.NonNullDate,
Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()),
Resolver = Resolve(x => x.LastModified),
Description = "The date and time when the asset has been modified last."
});

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs

@ -9,10 +9,10 @@ using System.Collections.Generic;
using System.Linq;
using GraphQL.Resolvers;
using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
fieldGraphType.Description = $"The structure of the {fieldName} field of the {schemaName} content type.";
var fieldResolver = new FuncFieldResolver<NamedContentData, IReadOnlyDictionary<string, JToken>>(c =>
var fieldResolver = new FuncFieldResolver<NamedContentData, IReadOnlyDictionary<string, IJsonValue>>(c =>
{
return c.Source.GetOrDefault(field.Name);
});

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs

@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
Name = "created",
ResolvedType = AllTypes.NonNullDate,
Resolver = Resolve(x => x.Created.ToDateTimeUtc()),
Resolver = Resolve(x => x.Created),
Description = $"The date and time when the {schemaName} content has been created."
});
@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
Name = "lastModified",
ResolvedType = AllTypes.NonNullDate,
Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()),
Resolver = Resolve(x => x.LastModified),
Description = $"The date and time when the {schemaName} content has been modified last."
});

6
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs

@ -8,14 +8,14 @@
using System.Linq;
using GraphQL.Resolvers;
using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class NestedGraphType : ObjectGraphType<JObject>
public sealed class NestedGraphType : ObjectGraphType<JsonObject>
{
public NestedGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field)
{
@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
var resolver = new FuncFieldResolver<object>(c =>
{
if (((JObject)c.Source).TryGetValue(nestedField.Name, out var value))
if (((JsonObject)c.Source).TryGetValue(nestedField.Name, out var value))
{
return fieldInfo.Resolver(value, c);
}

4
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs

@ -7,13 +7,13 @@
using System;
using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public delegate object ValueResolver(JToken value, ResolveFieldContext context);
public delegate object ValueResolver(IJsonValue value, ResolveFieldContext context);
public sealed class QueryGraphTypeVisitor : IFieldVisitor<(IGraphType ResolveType, ValueResolver Resolver)>
{

41
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantGraphType.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Language.AST;
using GraphQL.Types;
using NodaTime.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
public sealed class InstantGraphType : DateGraphType
{
public override object Serialize(object value)
{
return ParseValue(value);
}
public override object ParseValue(object value)
{
return InstantPattern.General.Parse(value.ToString()).Value;
}
public override object ParseLiteral(IValue value)
{
if (value is InstantValue timeValue)
{
return ParseValue(timeValue.Value);
}
if (value is StringValue stringValue)
{
return ParseValue(stringValue.Value);
}
return null;
}
}
}

25
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantValue.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Language.AST;
using NodaTime;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
public sealed class InstantValue : ValueNode<Instant>
{
public InstantValue(Instant value)
{
Value = value;
}
protected override bool Equals(ValueNode<Instant> node)
{
return Value.Equals(node.Value);
}
}
}

6
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs

@ -7,7 +7,7 @@
using GraphQL.Language.AST;
using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
@ -21,12 +21,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
public IValue Convert(object value, IGraphType type)
{
return new JsonValue(value as JObject);
return new JsonValue(value as JsonObject);
}
public bool Matches(object value, IGraphType type)
{
return value is JObject;
return value is JsonObject;
}
}
}

8
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs

@ -6,18 +6,18 @@
// ==========================================================================
using GraphQL.Language.AST;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
public sealed class JsonValue : ValueNode<JObject>
public sealed class JsonValue : ValueNode<IJsonValue>
{
public JsonValue(JObject value)
public JsonValue(IJsonValue value)
{
Value = value;
}
protected override bool Equals(ValueNode<JObject> node)
protected override bool Equals(ValueNode<IJsonValue> node)
{
return false;
}

18
src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs

@ -6,7 +6,7 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.State
{
public class ContentState : DomainObjectState<ContentState>, IContentEntity
{
[JsonProperty]
[DataMember]
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
[DataMember]
public NamedId<Guid> SchemaId { get; set; }
[JsonProperty]
[DataMember]
public NamedContentData Data { get; set; }
[JsonProperty]
[DataMember]
public NamedContentData DataDraft { get; set; }
[JsonProperty]
[DataMember]
public ScheduleJob ScheduleJob { get; set; }
[JsonProperty]
[DataMember]
public bool IsPending { get; set; }
[JsonProperty]
[DataMember]
public bool IsDeleted { get; set; }
[JsonProperty]
[DataMember]
public Status Status { get; set; }
protected void On(ContentCreated @event)

14
src/Squidex.Domain.Apps.Entities/DomainObjectState.cs

@ -6,7 +6,7 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using NodaTime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
@ -24,22 +24,22 @@ namespace Squidex.Domain.Apps.Entities
IUpdateableEntityWithLastModifiedBy
where T : Cloneable
{
[JsonProperty]
[DataMember]
public Guid Id { get; set; }
[JsonProperty]
[DataMember]
public RefToken CreatedBy { get; set; }
[JsonProperty]
[DataMember]
public RefToken LastModifiedBy { get; set; }
[JsonProperty]
[DataMember]
public Instant Created { get; set; }
[JsonProperty]
[DataMember]
public Instant LastModified { get; set; }
[JsonProperty]
[DataMember]
public long Version { get; set; } = EtagVersion.Empty;
public T Clone()

8
src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs

@ -6,7 +6,7 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules;
@ -20,13 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.State
[CollectionName("Rules")]
public class RuleState : DomainObjectState<RuleState>, IRuleEntity
{
[JsonProperty]
[DataMember]
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
[DataMember]
public Rule RuleDef { get; set; }
[JsonProperty]
[DataMember]
public bool IsDeleted { get; set; }
protected void On(RuleCreated @event)

28
src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs

@ -6,7 +6,7 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events;
@ -22,43 +22,43 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
[CollectionName("Schemas")]
public class SchemaState : DomainObjectState<SchemaState>, ISchemaEntity
{
[JsonProperty]
[DataMember]
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
[DataMember]
public string Name { get; set; }
[JsonProperty]
[DataMember]
public string Category { get; set; }
[JsonProperty]
[DataMember]
public int TotalFields { get; set; }
[JsonProperty]
[DataMember]
public bool IsDeleted { get; set; }
[JsonProperty]
[DataMember]
public bool IsSingleton { get; set; }
[JsonProperty]
[DataMember]
public string ScriptQuery { get; set; }
[JsonProperty]
[DataMember]
public string ScriptCreate { get; set; }
[JsonProperty]
[DataMember]
public string ScriptUpdate { get; set; }
[JsonProperty]
[DataMember]
public string ScriptDelete { get; set; }
[JsonProperty]
[DataMember]
public string ScriptChange { get; set; }
[JsonProperty]
[DataMember]
public Schema SchemaDef { get; set; }
[JsonIgnore]
[IgnoreDataMember]
public bool IsPublished
{
get { return SchemaDef.IsPublished; }

2
src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs

@ -17,4 +17,4 @@ namespace Squidex.Domain.Apps.Events.Rules
public RuleAction Action { get; set; }
}
}
}

2
src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs

@ -19,4 +19,4 @@ namespace Squidex.Domain.Apps.Events.Schemas
public FieldProperties Properties { get; set; }
}
}
}

5
src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Globalization;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events
@ -15,12 +14,12 @@ namespace Squidex.Domain.Apps.Events
{
public static Guid AppId(this EnvelopeHeaders headers)
{
return headers[SquidexHeaders.AppId].ToGuid(CultureInfo.InvariantCulture);
return headers.GetGuid(SquidexHeaders.AppId);
}
public static Envelope<T> SetAppId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(SquidexHeaders.AppId, value);
envelope.Headers.Add(SquidexHeaders.AppId, value.ToString());
return envelope;
}

21
src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs

@ -8,20 +8,23 @@
using System;
using System.Text;
using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using EventStoreData = EventStore.ClientAPI.EventData;
namespace Squidex.Infrastructure.EventSourcing
{
public static class Formatter
{
public static StoredEvent Read(ResolvedEvent resolvedEvent)
public static StoredEvent Read(ResolvedEvent resolvedEvent, IJsonSerializer serializer)
{
var @event = resolvedEvent.Event;
var body = Encoding.UTF8.GetString(@event.Data);
var meta = Encoding.UTF8.GetString(@event.Metadata);
var metadata = Encoding.UTF8.GetString(@event.Data);
var eventData = new EventData { Type = @event.EventType, Payload = body, Metadata = meta };
var headersJson = Encoding.UTF8.GetString(@event.Metadata);
var headers = serializer.Deserialize<EnvelopeHeaders>(headersJson);
var eventData = new EventData(@event.EventType, headers, metadata);
return new StoredEvent(
@event.EventStreamId,
@ -30,12 +33,14 @@ namespace Squidex.Infrastructure.EventSourcing
eventData);
}
public static EventStoreData Write(EventData eventData)
public static EventStoreData Write(EventData eventData, IJsonSerializer serializer)
{
var body = Encoding.UTF8.GetBytes(eventData.Payload.ToString());
var meta = Encoding.UTF8.GetBytes(eventData.Metadata.ToString());
var payload = Encoding.UTF8.GetBytes(eventData.Payload);
var headersJson = serializer.Serialize(eventData.Headers);
var headersBytes = Encoding.UTF8.GetBytes(headersJson);
return new EventStoreData(Guid.NewGuid(), eventData.Type, true, body, meta);
return new EventStoreData(Guid.NewGuid(), eventData.Type, true, payload, headersBytes);
}
}
}

14
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.EventSourcing
@ -20,14 +21,17 @@ namespace Squidex.Infrastructure.EventSourcing
private const int WritePageSize = 500;
private const int ReadPageSize = 500;
private readonly IEventStoreConnection connection;
private readonly IJsonSerializer serializer;
private readonly string prefix;
private readonly ProjectionClient projectionClient;
public GetEventStore(IEventStoreConnection connection, string prefix, string projectionHost)
public GetEventStore(IEventStoreConnection connection, IJsonSerializer serializer, string prefix, string projectionHost)
{
Guard.NotNull(connection, nameof(connection));
Guard.NotNull(serializer, nameof(serializer));
this.connection = connection;
this.serializer = serializer;
this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex");
@ -50,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing
public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null)
{
return new GetEventStoreSubscription(connection, subscriber, projectionClient, position, streamFilter);
return new GetEventStoreSubscription(connection, subscriber, serializer, projectionClient, position, streamFilter);
}
public Task CreateIndexAsync(string property)
@ -95,7 +99,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events)
{
var storedEvent = Formatter.Read(resolved);
var storedEvent = Formatter.Read(resolved, serializer);
await callback(storedEvent);
}
@ -123,7 +127,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events)
{
var storedEvent = Formatter.Read(resolved);
var storedEvent = Formatter.Read(resolved, serializer);
result.Add(storedEvent);
}
@ -164,7 +168,7 @@ namespace Squidex.Infrastructure.EventSourcing
return;
}
var eventsToSave = events.Select(Formatter.Write).ToList();
var eventsToSave = events.Select(x => Formatter.Write(x, serializer)).ToList();
if (eventsToSave.Count < WritePageSize)
{

9
src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs

@ -8,6 +8,7 @@
using System.Threading.Tasks;
using EventStore.ClientAPI;
using EventStore.ClientAPI.Exceptions;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.EventSourcing
@ -16,12 +17,14 @@ namespace Squidex.Infrastructure.EventSourcing
{
private readonly IEventStoreConnection connection;
private readonly IEventSubscriber subscriber;
private readonly IJsonSerializer serializer;
private readonly EventStoreCatchUpSubscription subscription;
private readonly long? position;
public GetEventStoreSubscription(
IEventStoreConnection connection,
IEventSubscriber subscriber,
IJsonSerializer serializer,
ProjectionClient projectionClient,
string position,
string streamFilter)
@ -34,8 +37,10 @@ namespace Squidex.Infrastructure.EventSourcing
var streamName = projectionClient.CreateProjectionAsync(streamFilter).Result;
this.serializer = serializer;
this.subscriber = subscriber;
this.subscription = SubscribeToStream(streamName);
subscription = SubscribeToStream(streamName);
}
public Task StopAsync()
@ -56,7 +61,7 @@ namespace Squidex.Infrastructure.EventSourcing
return connection.SubscribeToStreamFrom(streamName, position, settings,
(s, e) =>
{
var storedEvent = Formatter.Read(e);
var storedEvent = Formatter.Read(e, serializer);
subscriber.OnEventAsync(this, storedEvent).Wait();
}, null,

11
src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.EventSourcing
@ -21,18 +21,19 @@ namespace Squidex.Infrastructure.EventSourcing
[BsonRequired]
public string Payload { get; set; }
[BsonElement]
[BsonElement("Metadata")]
[BsonRequired]
public JToken Metadata { get; set; }
[BsonJson]
public EnvelopeHeaders Headers { get; set; }
public static MongoEvent FromEventData(EventData data)
{
return new MongoEvent { Type = data.Type, Metadata = data.Metadata, Payload = data.Payload.ToString() };
return new MongoEvent { Type = data.Type, Headers = data.Headers, Payload = data.Payload };
}
public EventData ToEventData()
{
return new EventData { Type = Type, Metadata = Metadata, Payload = JObject.Parse(Payload) };
return new EventData(Type, Headers, Payload);
}
}
}

155
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs

@ -1,155 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Globalization;
using MongoDB.Bson;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.MongoDb
{
public static class BsonJsonConverter
{
public static BsonDocument ToBson(this JObject source)
{
var result = new BsonDocument();
foreach (var property in source)
{
result.Add(property.Key.EscapeJson(), property.Value.ToBson());
}
return result;
}
public static JObject ToJson(this BsonDocument source)
{
var result = new JObject();
foreach (var property in source)
{
result.Add(property.Name.UnescapeBson(), property.Value.ToJson());
}
return result;
}
public static BsonArray ToBson(this JArray source)
{
var result = new BsonArray();
foreach (var item in source)
{
result.Add(item.ToBson());
}
return result;
}
public static JArray ToJson(this BsonArray source)
{
var result = new JArray();
foreach (var item in source)
{
result.Add(item.ToJson());
}
return result;
}
public static BsonValue ToBson(this JToken source)
{
switch (source.Type)
{
case JTokenType.Object:
return ((JObject)source).ToBson();
case JTokenType.Array:
return ((JArray)source).ToBson();
case JTokenType.Integer:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Float:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.String:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Boolean:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Null:
return BsonNull.Value;
case JTokenType.Undefined:
return BsonUndefined.Value;
case JTokenType.Bytes:
return BsonValue.Create(((JValue)source).Value);
case JTokenType.Guid:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.Uri:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.TimeSpan:
return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture));
case JTokenType.Date:
{
var value = ((JValue)source).Value;
if (value is DateTime dateTime)
{
return dateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
else if (value is DateTimeOffset dateTimeOffset)
{
if (dateTimeOffset.Offset == TimeSpan.Zero)
{
return dateTimeOffset.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
else
{
return dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture);
}
}
else
{
return value.ToString();
}
}
}
throw new NotSupportedException($"Cannot convert {source.GetType()} to Bson.");
}
public static JToken ToJson(this BsonValue source)
{
switch (source.BsonType)
{
case BsonType.Document:
return source.AsBsonDocument.ToJson();
case BsonType.Array:
return source.AsBsonArray.ToJson();
case BsonType.Double:
return new JValue(source.AsDouble);
case BsonType.String:
return new JValue(source.AsString);
case BsonType.Boolean:
return new JValue(source.AsBoolean);
case BsonType.DateTime:
return new JValue(source.ToUniversalTime());
case BsonType.Int32:
return new JValue(source.AsInt32);
case BsonType.Int64:
return new JValue(source.AsInt64);
case BsonType.Decimal128:
return new JValue(source.AsDecimal);
case BsonType.Binary:
return new JValue(source.AsBsonBinaryData.Bytes);
case BsonType.Null:
return JValue.CreateNull();
case BsonType.Undefined:
return JValue.CreateUndefined();
}
throw new NotSupportedException($"Cannot convert {source.GetType()} to Json.");
}
}
}

20
src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs

@ -37,16 +37,6 @@ namespace Squidex.Infrastructure.MongoDb
{
SetToken(NewtonsoftJsonToken.PropertyName, bsonReader.ReadName().UnescapeBson());
}
else if (bsonReader.State == BsonReaderState.EndOfDocument)
{
SetToken(NewtonsoftJsonToken.EndObject);
bsonReader.ReadEndDocument();
}
else if (bsonReader.State == BsonReaderState.EndOfArray)
{
SetToken(NewtonsoftJsonToken.EndArray);
bsonReader.ReadEndArray();
}
else if (bsonReader.State == BsonReaderState.Value)
{
switch (bsonReader.CurrentBsonType)
@ -95,6 +85,16 @@ namespace Squidex.Infrastructure.MongoDb
throw new NotSupportedException();
}
}
else if (bsonReader.State == BsonReaderState.EndOfDocument)
{
SetToken(NewtonsoftJsonToken.EndObject);
bsonReader.ReadEndDocument();
}
else if (bsonReader.State == BsonReaderState.EndOfArray)
{
SetToken(NewtonsoftJsonToken.EndArray);
bsonReader.ReadEndArray();
}
if (bsonReader.State == BsonReaderState.Initial)
{

5
src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs

@ -101,10 +101,7 @@ namespace Squidex.Infrastructure.MongoDb
{
var update = updater(Builders<T>.Update.Set(x => x.Version, newVersion));
await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion,
update
.Set(x => x.Version, newVersion),
Upsert);
await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, update, Upsert);
}
catch (MongoWriteException ex)
{

12
src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs

@ -9,16 +9,16 @@ using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RabbitMQ.Client;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class RabbitMqEventConsumer : DisposableObjectBase, IInitializable, IEventConsumer
{
private readonly JsonSerializerSettings serializerSettings;
private readonly IJsonSerializer jsonSerializer;
private readonly string eventPublisherName;
private readonly string exchange;
private readonly string eventsFilter;
@ -36,12 +36,12 @@ namespace Squidex.Infrastructure.CQRS.Events
get { return eventsFilter; }
}
public RabbitMqEventConsumer(JsonSerializerSettings serializerSettings, string eventPublisherName, string uri, string exchange, string eventsFilter)
public RabbitMqEventConsumer(IJsonSerializer jsonSerializer, string eventPublisherName, string uri, string exchange, string eventsFilter)
{
Guard.NotNullOrEmpty(uri, nameof(uri));
Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName));
Guard.NotNullOrEmpty(exchange, nameof(exchange));
Guard.NotNull(serializerSettings, nameof(serializerSettings));
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
connectionFactory = new ConnectionFactory { Uri = new Uri(uri, UriKind.Absolute) };
connection = new Lazy<IConnection>(connectionFactory.CreateConnection);
@ -49,8 +49,8 @@ namespace Squidex.Infrastructure.CQRS.Events
this.exchange = exchange;
this.eventsFilter = eventsFilter;
this.jsonSerializer = jsonSerializer;
this.eventPublisherName = eventPublisherName;
this.serializerSettings = serializerSettings;
}
protected override void DisposeObject(bool disposing)
@ -88,7 +88,7 @@ namespace Squidex.Infrastructure.CQRS.Events
public Task On(Envelope<IEvent> @event)
{
var jsonString = JsonConvert.SerializeObject(@event, serializerSettings);
var jsonString = jsonSerializer.Serialize(@event);
var jsonBytes = Encoding.UTF8.GetBytes(jsonString);
channel.Value.BasicPublish(exchange, string.Empty, null, jsonBytes);

9
src/Squidex.Infrastructure.Redis/RedisPubSub.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks;
using StackExchange.Redis;
@ -19,11 +20,13 @@ namespace Squidex.Infrastructure
{
private readonly ConcurrentDictionary<string, object> subscriptions = new ConcurrentDictionary<string, object>();
private readonly Lazy<IConnectionMultiplexer> redisClient;
private readonly IJsonSerializer serializer;
private readonly Lazy<ISubscriber> redisSubscriber;
private readonly ISemanticLog log;
public RedisPubSub(Lazy<IConnectionMultiplexer> redis, ISemanticLog log)
public RedisPubSub(Lazy<IConnectionMultiplexer> redis, IJsonSerializer serializer, ISemanticLog log)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(redis, nameof(redis));
Guard.NotNull(log, nameof(log));
@ -31,6 +34,8 @@ namespace Squidex.Infrastructure
redisClient = redis;
redisSubscriber = new Lazy<ISubscriber>(() => redis.Value.GetSubscriber());
this.serializer = serializer;
}
public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
@ -61,7 +66,7 @@ namespace Squidex.Infrastructure
{
var typeName = typeof(T).FullName;
return (RedisSubscription<T>)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription<T>(c.redisSubscriber.Value, k, c.log));
return (RedisSubscription<T>)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription<T>(c.redisSubscriber.Value, serializer, k, c.log));
}
}
}

10
src/Squidex.Infrastructure.Redis/RedisSubscription.cs

@ -7,7 +7,7 @@
using System;
using System.Reactive.Subjects;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using StackExchange.Redis;
@ -20,6 +20,7 @@ namespace Squidex.Infrastructure
private readonly Guid selfId = Guid.NewGuid();
private readonly Subject<T> subject = new Subject<T>();
private readonly ISubscriber subscriber;
private readonly IJsonSerializer serializer;
private readonly ISemanticLog log;
private readonly string channelName;
@ -30,10 +31,11 @@ namespace Squidex.Infrastructure
public Guid Sender;
}
public RedisSubscription(ISubscriber subscriber, string channelName, ISemanticLog log)
public RedisSubscription(ISubscriber subscriber, IJsonSerializer serializer, string channelName, ISemanticLog log)
{
this.log = log;
this.serializer = serializer;
this.subscriber = subscriber;
this.subscriber.Subscribe(channelName, (channel, value) => HandleMessage(value));
@ -46,7 +48,7 @@ namespace Squidex.Infrastructure
{
var senderId = notifySelf ? Guid.Empty : selfId;
var envelope = JsonConvert.SerializeObject(new Envelope { Sender = senderId, Payload = (T)value });
var envelope = serializer.Serialize(new Envelope { Sender = senderId, Payload = (T)value });
subscriber.Publish(channelName, envelope);
}
@ -68,7 +70,7 @@ namespace Squidex.Infrastructure
return;
}
var envelope = JsonConvert.DeserializeObject<Envelope>(value);
var envelope = serializer.Deserialize<Envelope>(value);
if (envelope.Sender != selfId)
{

2
src/Squidex.Infrastructure/Assets/AssetFile.cs

@ -7,7 +7,6 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Assets
{
@ -21,7 +20,6 @@ namespace Squidex.Infrastructure.Assets
public long FileSize { get; }
[JsonConstructor]
public AssetFile(string fileName, string mimeType, long fileSize, Func<Stream> openAction)
{
Guard.NotNullOrEmpty(fileName, nameof(fileName));

2
src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs

@ -221,7 +221,7 @@ namespace Squidex.Infrastructure.Commands
{
var result = await ExecuteAsync(command.Value);
return result.AsJ();
return result;
}
protected abstract Task<object> ExecuteAsync(IAggregateCommand command);

34
src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs

@ -6,38 +6,36 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.Json;
namespace Squidex.Infrastructure.EventSourcing
{
public class DefaultEventDataFormatter : IEventDataFormatter
public sealed class DefaultEventDataFormatter : IEventDataFormatter
{
private readonly JsonSerializer serializer;
private readonly IJsonSerializer serializer;
private readonly TypeNameRegistry typeNameRegistry;
public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, JsonSerializer serializer = null)
public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer)
{
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
Guard.NotNull(serializer, nameof(serializer));
this.typeNameRegistry = typeNameRegistry;
this.serializer = serializer ?? JsonSerializer.CreateDefault();
this.serializer = serializer;
}
public Envelope<IEvent> Parse(EventData eventData, bool migrate = true)
public Envelope<IEvent> Parse(EventData eventData, Func<string, string> stringConverter = null)
{
var eventType = typeNameRegistry.GetType(eventData.Type);
var payloadType = typeNameRegistry.GetType(eventData.Type);
var payloadObj = serializer.Deserialize<IEvent>(eventData.Payload, payloadType, stringConverter);
var headers = eventData.Metadata.ToObject<EnvelopeHeaders>(serializer);
var content = eventData.Payload.ToObject(eventType, serializer) as IEvent;
if (migrate && content is IMigratedEvent migratedEvent)
if (payloadObj is IMigratedEvent migratedEvent)
{
content = migratedEvent.Migrate();
payloadObj = migratedEvent.Migrate();
}
var envelope = new Envelope<IEvent>(content, headers);
var envelope = new Envelope<IEvent>(payloadObj, eventData.Headers);
return envelope;
}
@ -51,14 +49,12 @@ namespace Squidex.Infrastructure.EventSourcing
eventPayload = migratedEvent.Migrate();
}
var eventType = typeNameRegistry.GetName(eventPayload.GetType());
var payloadType = typeNameRegistry.GetName(eventPayload.GetType());
var payloadJson = serializer.Serialize(envelope.Payload);
envelope.SetCommitId(commitId);
var headers = JToken.FromObject(envelope.Headers, serializer);
var content = JToken.FromObject(envelope.Payload, serializer);
return new EventData { Type = eventType, Payload = content, Metadata = headers };
return new EventData(payloadType, envelope.Headers, payloadJson);
}
}
}

79
src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs

@ -8,6 +8,8 @@
using System;
using System.Globalization;
using NodaTime;
using NodaTime.Text;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing
{
@ -15,74 +17,127 @@ namespace Squidex.Infrastructure.EventSourcing
{
public static string EventPosition(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventNumber].ToString();
return headers.GetString(CommonHeaders.EventNumber);
}
public static Envelope<T> SetEventPosition<T>(this Envelope<T> envelope, string value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventNumber, value);
envelope.Headers.Add(CommonHeaders.EventNumber, value);
return envelope;
}
public static long EventStreamNumber(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventStreamNumber].ToInt64(CultureInfo.InvariantCulture);
return headers.GetLong(CommonHeaders.EventStreamNumber);
}
public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventStreamNumber, value);
envelope.Headers.Add(CommonHeaders.EventStreamNumber, value);
return envelope;
}
public static Guid CommitId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.CommitId].ToGuid(CultureInfo.InvariantCulture);
return headers.GetGuid(CommonHeaders.CommitId);
}
public static Envelope<T> SetCommitId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.CommitId, value);
envelope.Headers.Add(CommonHeaders.CommitId, value.ToString());
return envelope;
}
public static Guid AggregateId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.AggregateId].ToGuid(CultureInfo.InvariantCulture);
return headers.GetGuid(CommonHeaders.AggregateId);
}
public static Envelope<T> SetAggregateId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.AggregateId, value);
envelope.Headers.Add(CommonHeaders.AggregateId, value.ToString());
return envelope;
}
public static Guid EventId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.EventId].ToGuid(CultureInfo.InvariantCulture);
return headers.GetGuid(CommonHeaders.EventId);
}
public static Envelope<T> SetEventId<T>(this Envelope<T> envelope, Guid value) where T : class
{
envelope.Headers.Set(CommonHeaders.EventId, value);
envelope.Headers.Add(CommonHeaders.EventId, value.ToString());
return envelope;
}
public static Instant Timestamp(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.Timestamp].ToInstant(CultureInfo.InvariantCulture);
return headers.GetInstant(CommonHeaders.Timestamp);
}
public static Envelope<T> SetTimestamp<T>(this Envelope<T> envelope, Instant value) where T : class
{
envelope.Headers.Set(CommonHeaders.Timestamp, value);
envelope.Headers.Add(CommonHeaders.Timestamp, value.ToString());
return envelope;
}
public static long GetLong(this JsonObject obj, string key)
{
if (obj.TryGetValue(key, out var v))
{
if (v is JsonNumber number)
{
return (long)number.Value;
}
else if (v.Type == JsonValueType.String && double.TryParse(v.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{
return (long)result;
}
}
return 0;
}
public static Guid GetGuid(this JsonObject obj, string key)
{
if (obj.TryGetValue(key, out var v))
{
if (v.Type == JsonValueType.String && Guid.TryParse(v.ToString(), out var guid))
{
return guid;
}
}
return default(Guid);
}
public static Instant GetInstant(this JsonObject obj, string key)
{
if (obj.TryGetValue(key, out var v))
{
if (v.Type == JsonValueType.String && InstantPattern.General.Parse(v.ToString()).TryGetValue(default(Instant), out var instant))
{
return instant;
}
}
return default(Instant);
}
public static string GetString(this JsonObject obj, string key)
{
if (obj.TryGetValue(key, out var v))
{
return v.ToString();
}
return string.Empty;
}
}
}

25
src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs

@ -5,37 +5,24 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing
{
public sealed class EnvelopeHeaders : PropertiesBag
public sealed class EnvelopeHeaders : JsonObject
{
public EnvelopeHeaders()
{
}
public EnvelopeHeaders(PropertiesBag bag)
public EnvelopeHeaders(JsonObject headers)
: base(headers)
{
if (bag == null)
{
return;
}
foreach (var property in bag.Properties)
{
Set(property.Key, property.Value.RawValue);
}
}
public EnvelopeHeaders Clone()
{
var clone = new EnvelopeHeaders();
foreach (var property in Properties)
{
clone.Set(property.Key, property.Value.RawValue);
}
return clone;
return new EnvelopeHeaders(this);
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save