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}"; 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; ruleJob.Content["objectID"] = contentId;
} }

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

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

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

@ -8,7 +8,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Elasticsearch.Net; using Elasticsearch.Net;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -60,8 +59,9 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{ {
ruleDescription = $"Upsert to index: {action.IndexName}"; ruleDescription = $"Upsert to index: {action.IndexName}";
ruleJob.Content = ToPayload(contentEvent); var json = ToJson(contentEvent);
ruleJob.Content["objectID"] = contentId;
ruleJob.Content = $"{{ \"objectId\": \"{contentId}\", {json.Substring(1)}";
} }
ruleJob.Username = action.Username; ruleJob.Username = action.Username;
@ -86,9 +86,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{ {
if (job.Content != null) if (job.Content != null)
{ {
var doc = job.Content.ToString(); var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, job.Content);
var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }
@ -116,10 +114,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch
public string ContentId { get; set; } public string ContentId { get; set; }
public string Content { get; set; }
public string IndexName { get; set; } public string IndexName { get; set; }
public string IndexType { 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.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
using Squidex.Infrastructure.Json;
namespace Squidex.Extensions.Actions.Medium namespace Squidex.Extensions.Actions.Medium
{ {
@ -22,53 +21,61 @@ namespace Squidex.Extensions.Actions.Medium
private const string Description = "Post to medium"; private const string Description = "Post to medium";
private readonly IHttpClientFactory httpClientFactory; 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) : base(formatter)
{ {
this.httpClientFactory = httpClientFactory; this.httpClientFactory = httpClientFactory;
this.serializer = serializer;
} }
protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action)
{ {
var requestBody = var ruleJob = new MediumJob { AccessToken = action.AccessToken, PublicationId = action.PublicationId };
new JObject(
new JProperty("title", Format(action.Title, @event)), var requestBody = new
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
{ {
AccessToken = action.AccessToken, title = Format(action.Title, @event),
PublicationId = action.PublicationId, contentFormat = action.IsHtml ? "html" : "markdown",
RequestBody = requestBody.ToString(Formatting.Indented) content = Format(action.Content, @event),
canonicalUrl = Format(action.CanonicalUrl, @event),
tags = ParseTags(@event, action)
}; };
ruleJob.RequestBody = ToJson(requestBody);
return (Description, ruleJob); return (Description, ruleJob);
} }
private JArray ParseTags(EnrichedEvent @event, MediumAction action) private string[] ParseTags(EnrichedEvent @event, MediumAction action)
{ {
if (string.IsNullOrWhiteSpace(action.Tags)) if (string.IsNullOrWhiteSpace(action.Tags))
{ {
return null; return null;
} }
string[] tags;
try try
{ {
var jsonTags = Format(action.Tags, @event); var jsonTags = Format(action.Tags, @event);
tags = JsonConvert.DeserializeObject<string[]>(jsonTags); return serializer.Deserialize<string[]>(jsonTags);
} }
catch catch
{ {
tags = action.Tags.Split(','); return action.Tags.Split(',');
} }
return new JArray(tags);
} }
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) 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); response = await httpClient.SendAsync(meRequest);
var responseString = await response.Content.ReadAsStringAsync(); 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"; 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.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -29,12 +28,10 @@ namespace Squidex.Extensions.Actions.Prerender
{ {
var url = Format(action.Url, @event); var url = Format(action.Url, @event);
var request = var request = new { prerenderToken = action.Token, url };
new JObject( var requestBody = ToJson(request);
new JProperty("prerenderToken", action.Token),
new JProperty("url", url));
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) 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.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -33,14 +31,12 @@ namespace Squidex.Extensions.Actions.Slack
protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action)
{ {
var body = var body = new { text = Format(action.Text, @event) };
new JObject(
new JProperty("text", Format(action.Text, @event)));
var ruleJob = new SlackJob var ruleJob = new SlackJob
{ {
RequestUrl = action.WebhookUrl.ToString(), RequestUrl = action.WebhookUrl.ToString(),
RequestBody = body.ToString(Formatting.Indented) RequestBody = ToJson(body)
}; };
return (Description, ruleJob); 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json 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 System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json 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 Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using System; using System;
using System.Collections.Generic; 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.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
var resultValue = new ContentFieldData(); 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; resultValue[partitionValue.Key] = partitionValue.Value;
} }

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

@ -7,21 +7,24 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents 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() public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase) : 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)); Guard.NotNullOrEmpty(key, nameof(key));
@ -30,11 +33,6 @@ namespace Squidex.Domain.Apps.Core.Contents
return this; return this;
} }
public ContentFieldData AddValue(JToken value)
{
return AddValue(InvariantPartitioning.Instance.Master.Key, value);
}
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return Equals(obj as ContentFieldData); return Equals(obj as ContentFieldData);
@ -42,12 +40,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public bool Equals(ContentFieldData other) 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() 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 System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Rules.Json namespace Squidex.Domain.Apps.Core.Rules.Json
{ {

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

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
@ -23,12 +22,12 @@ namespace Squidex.Domain.Apps.Core.Rules
public string ActionName { get; set; } public string ActionName { get; set; }
public string ActionData { get; set; }
public string Description { get; set; } public string Description { get; set; }
public Instant Created { get; set; } public Instant Created { get; set; }
public Instant Expires { 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 System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Schemas.Json 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
@ -41,11 +40,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return result; 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) if (text.Length <= maxFieldLength)
{ {
@ -57,18 +56,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
stringBuilder.Append(text); 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); 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList(); languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
} }
var result = new Dictionary<string, JToken>(); var result = new Dictionary<string, IJsonValue>();
foreach (var fieldValue in content) foreach (var fieldValue in content)
{ {
@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var language in languagePreferences) 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; result[fieldValue.Key] = value;

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

@ -8,13 +8,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable RECS0002 // Convert anonymous method to method group #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) foreach (var value in data.Values)
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
continue; continue;
} }
@ -78,13 +77,13 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
foreach (var partition in data) 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++) for (var i = 0; i < array.Count; i++)
{ {
var id = array[i].ToString(); 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) foreach (var partition in data)
{ {
if (!(partition.Value is JArray jArray)) if (!(partition.Value is JsonArray array))
{ {
continue; 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) 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public static class Value 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;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent 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 class ValueConverters
{ {
public static ValueConverter DecodeJson() public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer)
{ {
return (value, field) => 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; return value;
}; };
} }
public static ValueConverter EncodeJson() public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer)
{ {
return (value, field) => 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; return value;
@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
return (value, field) => return (value, field) =>
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
return value; return value;
} }

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

@ -5,12 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent namespace Squidex.Domain.Apps.Core.EnrichContent
{ {
@ -56,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant()); var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant());
if (field.RawProperties.IsRequired || defaultValue.IsNull()) if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null)
{ {
return; return;
} }
@ -65,13 +64,13 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value)) 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 System.Globalization;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent namespace Squidex.Domain.Apps.Core.EnrichContent
{ {
public sealed class DefaultValueFactory : IFieldVisitor<JToken> public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue>
{ {
private readonly Instant now; private readonly Instant now;
@ -22,71 +22,71 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
this.now = now; this.now = now;
} }
public static JToken CreateDefaultValue(IField field, Instant now) public static IJsonValue CreateDefaultValue(IField field, Instant now)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
return field.Accept(new DefaultValueFactory(now)); 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) if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now)
{ {
return now.ToString(); return JsonValue.Create(now.ToString());
} }
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today) 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.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
continue; 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); var ids = field.ExtractReferences(partitionValue.Value);

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

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

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

@ -7,22 +7,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesExtensions 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); 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; return value;
} }
@ -30,31 +29,27 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return ReferencesCleaner.CleanReferences(field, value, oldReferences); 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) foreach (var id in ids)
{ {
result.Add(new JValue(id)); result.Add(JsonValue.Create(id.ToString()));
} }
return result; 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>(); var result = new HashSet<Guid>();
foreach (var id in ids) foreach (var id in array)
{ {
if (id.Type == JTokenType.Guid) if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid))
{
result.Add((Guid)id);
}
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid))
{ {
result.Add(guid); result.Add(guid);
} }

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

@ -8,21 +8,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>> 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; 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)); return field.Accept(new ReferencesExtractor(value));
} }
@ -31,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
var result = new List<Guid>(); 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) foreach (var nestedField in field.Fields)
{ {

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

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

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

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

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

@ -7,7 +7,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
@ -17,8 +16,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
Type ActionType { get; } 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -25,29 +24,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TAction); } get { return typeof(TAction); }
} }
protected RuleActionHandler(RuleEventFormatter formatter) Type IRuleActionHandler.DataType
{ {
Guard.NotNull(formatter, nameof(formatter)); get { return typeof(TData); }
this.formatter = formatter;
} }
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) this.formatter = formatter;
{
return formatter.ToEnvelope(@event).ToString();
} }
protected virtual JObject ToPayload<T>(T @event) protected virtual string ToJson<T>(T @event)
{ {
return formatter.ToPayload(@event); return formatter.ToPayload(@event);
} }
protected virtual JObject ToEnvelope(EnrichedEvent @event) protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{ {
return formatter.ToEnvelope(@event); return formatter.ToEnvelope(@event);
} }
@ -62,16 +56,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return formatter.Format(text, @event); 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); 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); 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.Globalization;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules 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 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 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 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; 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)); Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.serializer = serializer; this.jsonSerializer = jsonSerializer;
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId); AddPattern("APP_ID", AppId);
@ -55,17 +55,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
patterns.Add((placeholder.ToCharArray(), generator)); 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( return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp });
new JProperty("type", @event.Name),
new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Timestamp.ToString()));
} }
public string Format(string text, EnrichedEvent @event) 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++) 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; 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 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; return Undefined;
} }
if (value is JValue jValue) return value.ToString() ?? Undefined;
{
return jValue.Value.ToString();
}
return value.ToString(Formatting.Indented) ?? Undefined;
} }
} }
} }

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

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

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

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

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

@ -9,61 +9,52 @@ using System;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Native.Object; using Jint.Native.Object;
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
public static class JsonMapper public static class JsonMapper
{ {
public static JsValue Map(JToken value, Engine engine) public static JsValue Map(IJsonValue value, Engine engine)
{ {
if (value == null) if (value == null)
{ {
return JsValue.Null; return JsValue.Null;
} }
switch (value.Type) switch (value)
{ {
case JTokenType.Date: case JsonNull n:
case JTokenType.Guid:
case JTokenType.String:
case JTokenType.Uri:
case JTokenType.TimeSpan:
return new JsValue((string)value);
case JTokenType.Null:
return JsValue.Null; return JsValue.Null;
case JTokenType.Undefined: case JsonScalar<string> s:
return JsValue.Undefined; return new JsValue(s.Value);
case JTokenType.Integer: case JsonScalar<bool> b:
return new JsValue((long)value); return new JsValue(b.Value);
case JTokenType.Float: case JsonScalar<double> b:
return new JsValue((double)value); return new JsValue(b.Value);
case JTokenType.Boolean: case JsonObject obj:
return new JsValue((bool)value); return FromObject(obj, engine);
case JTokenType.Object: case JsonArray arr:
return FromObject(value, engine); return FromArray(arr, 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);
}
} }
throw new ArgumentException("Invalid json type.", nameof(value)); 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); var target = new ObjectInstance(engine);
foreach (var property in obj) foreach (var property in obj)
@ -74,69 +65,64 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return target; return target;
} }
public static JToken Map(JsValue value) public static IJsonValue Map(JsValue value)
{ {
if (value == null || value.IsNull()) if (value == null || value.IsNull() || value.IsUndefined())
{
return JValue.CreateNull();
}
if (value.IsUndefined())
{ {
return JValue.CreateUndefined(); return JsonValue.Null;
} }
if (value.IsString()) if (value.IsString())
{ {
return new JValue(value.AsString()); return JsonValue.Create(value.AsString());
} }
if (value.IsBoolean()) if (value.IsBoolean())
{ {
return new JValue(value.AsBoolean()); return JsonValue.Create(value.AsBoolean());
} }
if (value.IsNumber()) if (value.IsNumber())
{ {
return new JValue(value.AsNumber()); return JsonValue.Create(value.AsNumber());
} }
if (value.IsDate()) if (value.IsDate())
{ {
return new JValue(value.AsDate().ToDateTime()); return JsonValue.Create(value.AsDate().ToString());
} }
if (value.IsRegExp()) if (value.IsRegExp())
{ {
return JValue.CreateString(value.AsRegExp().Value?.ToString()); return JsonValue.Create(value.AsRegExp().Value?.ToString());
} }
if (value.IsArray()) if (value.IsArray())
{ {
var arr = value.AsArray(); var arr = value.AsArray();
var target = new JArray(); var result = JsonValue.Array();
for (var i = 0; i < arr.GetLength(); i++) 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()) if (value.IsObject())
{ {
var obj = value.AsObject(); var obj = value.AsObject();
var target = new JObject(); var result = JsonValue.Object();
foreach (var kvp in obj.GetOwnProperties()) 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)); 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> <ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.1" /> <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="NJsonSchema" Version="9.12.2" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Tags namespace Squidex.Domain.Apps.Core.Tags
{ {
@ -24,10 +24,10 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(newData, nameof(newData)); Guard.NotNull(newData, nameof(newData));
var newValues = new HashSet<string>(); var newValues = new HashSet<string>();
var newArrays = new List<JArray>(); var newArrays = new List<JsonArray>();
var oldValues = new HashSet<string>(); var oldValues = new HashSet<string>();
var oldArrays = new List<JArray>(); var oldArrays = new List<JsonArray>();
GetValues(schema, newValues, newArrays, newData); GetValues(schema, newValues, newArrays, newData);
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
if (normalized.TryGetValue(array[i].ToString(), out var result)) 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)); Guard.NotNull(schema, nameof(schema));
var tagsValues = new HashSet<string>(); var tagsValues = new HashSet<string>();
var tagsArrays = new List<JArray>(); var tagsArrays = new List<JsonArray>();
GetValues(schema, tagsValues, tagsArrays, datas); GetValues(schema, tagsValues, tagsArrays, datas);
@ -73,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
if (denormalized.TryGetValue(array[i].ToString(), out var result)) 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) foreach (var field in schema.Fields)
{ {
@ -109,14 +109,12 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
foreach (var partition in fieldData) 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)) if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue))
{ {
ExtractTags(nestedValue, values, arrays); 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace #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 public sealed class ContentValidator
{ {
private static readonly ContentFieldData DefaultFieldData = new ContentFieldData(); private static readonly ContentFieldData DefaultFieldData = new ContentFieldData();
private static readonly JToken DefaultValue = JValue.CreateNull();
private readonly Schema schema; private readonly Schema schema;
private readonly PartitionResolver partitionResolver; private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context; private readonly ValidationContext context;
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var type = isLanguage ? "language" : "invariant value"; 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;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NodaTime.Text; using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public sealed class JsonValueConverter : IFieldVisitor<object> 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; 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)); return field.Accept(new JsonValueConverter(json));
} }
public object Visit(IArrayField field) public object Visit(IArrayField field)
{ {
return value.ToObject<List<JObject>>(); return ConvertToObjectList();
} }
public object Visit(IField<AssetsFieldProperties> field) 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) 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) public object Visit(IField<DateTimeFieldProperties> field)
{ {
if (value.Type == JTokenType.String) if (value.Type == JsonValueType.String)
{ {
var parseResult = InstantPattern.General.Parse(value.ToString()); var parseResult = InstantPattern.General.Parse(value.ToString());
@ -62,31 +97,49 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IField<GeolocationFieldProperties> field) public object Visit(IField<GeolocationFieldProperties> field)
{ {
var geolocation = (JObject)value; if (value is JsonObject geolocation)
foreach (var property in geolocation.Properties())
{ {
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) && foreach (var propertyName in geolocation.Keys)
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
{ {
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"]; if (geolocation.TryGetValue("latitude", out var latValue) && latValue is JsonScalar<double> latNumber)
var lon = (double)geolocation["longitude"]; {
var lat = latNumber.Value;
if (!lat.IsBetween(-90, 90)) if (!lat.IsBetween(-90, 90))
{ {
throw new InvalidCastException("Latitude must be between -90 and 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)) if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonScalar<double> lonNumber)
{ {
throw new InvalidCastException("Longitude must be between -180 and 180."); 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) public object Visit(IField<JsonFieldProperties> field)
@ -94,24 +147,76 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return value; 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) foreach (var item in array)
{ {
return value.ToObject<List<Guid>>(); 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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
object typedValue = null; 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>(); 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) 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) foreach (var fieldData in values)
{ {

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

@ -8,11 +8,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent 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)); 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) 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.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
@ -22,24 +23,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return data.GetReferencedIds(schema).ToList(); 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, return result.ConvertId2Name(schema,
FieldConverters.ForValues( FieldConverters.ForValues(
ValueConverters.DecodeJson(), ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)), ValueReferencesConverter.CleanReferences(deletedIds)),
FieldConverters.ForNestedId2Name( FieldConverters.ForNestedId2Name(
ValueConverters.DecodeJson(), ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds))); 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, return result.ConvertName2Id(schema,
FieldConverters.ForValues( FieldConverters.ForValues(
ValueConverters.EncodeJson()), ValueConverters.EncodeJson(serializer)),
FieldConverters.ForNestedName2Id( 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.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -26,10 +27,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
private readonly string collectionName; private readonly string collectionName;
public MongoContentCollection(IMongoDatabase database, string collectionName) protected IJsonSerializer Serializer { get; }
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName)
: base(database) : base(database)
{ {
this.collectionName = collectionName; this.collectionName = collectionName;
Serializer = serializer;
} }
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken)) 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) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef); entity.ParseData(schema.SchemaDef, Serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); 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) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef); entity.ParseData(schema.SchemaDef, Serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); 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.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -28,8 +29,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal sealed class MongoContentDraftCollection : MongoContentCollection internal sealed class MongoContentDraftCollection : MongoContentCollection
{ {
public MongoContentDraftCollection(IMongoDatabase database) public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, "State_Content_Draft") : 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) await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef); contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity; return contentEntity;
} }
@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); 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); 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.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
@ -124,13 +125,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
get { return dataDraft; } 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) 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.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal sealed class MongoContentPublishedCollection : MongoContentCollection internal sealed class MongoContentPublishedCollection : MongoContentCollection
{ {
public MongoContentPublishedCollection(IMongoDatabase database) public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, "State_Content_Published") : 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) await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef); contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity; 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.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -26,17 +27,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
private readonly IMongoDatabase database; private readonly IMongoDatabase database;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
private readonly MongoContentDraftCollection contentsDraft; private readonly MongoContentDraftCollection contentsDraft;
private readonly MongoContentPublishedCollection contentsPublished; 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(appProvider, nameof(appProvider));
Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider; this.appProvider = appProvider;
contentsDraft = new MongoContentDraftCollection(database); this.serializer = serializer;
contentsPublished = new MongoContentPublishedCollection(database);
contentsDraft = new MongoContentDraftCollection(database, serializer);
contentsPublished = new MongoContentPublishedCollection(database, serializer);
this.database = database; 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 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; var idDraftData = idData;
if (!ReferenceEquals(value.Data, value.DataDraft)) 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 var content = SimpleMapper.Map(value, new MongoContentEntity

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

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

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

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
@ -16,6 +15,8 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Shared.Users; using Squidex.Shared.Users;
@ -27,6 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private const string SettingsFile = "Settings.json"; private const string SettingsFile = "Settings.json";
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly IJsonSerializer serializer;
private readonly IAppsByNameIndex appsByNameIndex; private readonly IAppsByNameIndex appsByNameIndex;
private readonly HashSet<string> contributors = new HashSet<string>(); private readonly HashSet<string> contributors = new HashSet<string>();
private Dictionary<string, string> usersWithEmail = new Dictionary<string, 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 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(grainFactory, nameof(grainFactory));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(userResolver, nameof(userResolver));
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
this.serializer = serializer;
this.userResolver = userResolver; this.userResolver = userResolver;
appsByNameIndex = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); appsByNameIndex = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
@ -162,14 +165,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadUsersAsync(BackupReader reader) 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) private async Task WriteUsersAsync(BackupWriter writer)
{ {
var json = JObject.FromObject(usersWithEmail); var json = usersWithEmail;
await writer.WriteJsonAsync(UsersFile, json); await writer.WriteJsonAsync(UsersFile, json);
} }
@ -183,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadSettingsAsync(BackupReader reader, Guid appId) 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) 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 System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans; using Orleans;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppUISettingsGrain : IGrainWithGuidKey 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); 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. // 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.Core.Apps;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
[CollectionName("Apps")] [CollectionName("Apps")]
public class AppState : DomainObjectState<AppState>, IAppEntity public class AppState : DomainObjectState<AppState>, IAppEntity
{ {
[JsonProperty] [DataMember]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty] [DataMember]
public Roles Roles { get; set; } = Roles.Empty; public Roles Roles { get; set; } = Roles.Empty;
[JsonProperty] [DataMember]
public AppPlan Plan { get; set; } public AppPlan Plan { get; set; }
[JsonProperty] [DataMember]
public AppClients Clients { get; set; } = AppClients.Empty; public AppClients Clients { get; set; } = AppClients.Empty;
[JsonProperty] [DataMember]
public AppPatterns Patterns { get; set; } = AppPatterns.Empty; public AppPatterns Patterns { get; set; } = AppPatterns.Empty;
[JsonProperty] [DataMember]
public AppContributors Contributors { get; set; } = AppContributors.Empty; public AppContributors Contributors { get; set; } = AppContributors.Empty;
[JsonProperty] [DataMember]
public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English; public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English;
[JsonProperty] [DataMember]
public bool IsArchived { get; set; } public bool IsArchived { get; set; }
protected void On(AppCreated @event) protected void On(AppCreated @event)

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

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State; 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) 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) private async Task BackupTagsAsync(Guid appId, BackupWriter writer)
{ {
var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets); 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) 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;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
@ -20,37 +20,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{ {
public class AssetState : DomainObjectState<AssetState>, IAssetEntity public class AssetState : DomainObjectState<AssetState>, IAssetEntity
{ {
[JsonProperty] [DataMember]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [DataMember]
public string FileName { get; set; } public string FileName { get; set; }
[JsonProperty] [DataMember]
public string MimeType { get; set; } public string MimeType { get; set; }
[JsonProperty] [DataMember]
public long FileVersion { get; set; } public long FileVersion { get; set; }
[JsonProperty] [DataMember]
public long FileSize { get; set; } public long FileSize { get; set; }
[JsonProperty] [DataMember]
public long TotalSize { get; set; } public long TotalSize { get; set; }
[JsonProperty] [DataMember]
public bool IsImage { get; set; } public bool IsImage { get; set; }
[JsonProperty] [DataMember]
public int? PixelWidth { get; set; } public int? PixelWidth { get; set; }
[JsonProperty] [DataMember]
public int? PixelHeight { get; set; } public int? PixelHeight { get; set; }
[JsonProperty] [DataMember]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[JsonProperty] [DataMember]
public HashSet<string> Tags { get; set; } public HashSet<string> Tags { get; set; }
Guid IAssetInfo.AssetId 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;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -34,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock; private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers; private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers, IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log, ISemanticLog log,
IStore<Guid> store) IStore<Guid> store)
{ {
@ -60,6 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers)); Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store)); Guard.NotNull(store, nameof(store));
Guard.NotNull(log, nameof(log)); Guard.NotNull(log, nameof(log));
@ -69,6 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers; this.handlers = handlers;
this.serializer = serializer;
this.store = store; this.store = store;
this.log = log; this.log = log;
} }
@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id)) 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 => await eventStore.QueryAsync(async storedEvent =>
{ {

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

@ -8,21 +8,25 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Helpers; using Squidex.Domain.Apps.Entities.Backup.Helpers;
using Squidex.Domain.Apps.Entities.Backup.Model;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
#pragma warning disable SA1401 // Fields must be private
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
public sealed class BackupReader : DisposableObjectBase public sealed class BackupReader : DisposableObjectBase
{ {
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly GuidMapper guidMapper = new GuidMapper(); private readonly GuidMapper guidMapper = new GuidMapper();
private readonly ZipArchive archive; private readonly ZipArchive archive;
private readonly IJsonSerializer serializer;
private int readEvents; private int readEvents;
private int readAttachments; private int readAttachments;
@ -36,8 +40,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
get { return readAttachments; } 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); archive = new ZipArchive(stream, ZipArchiveMode.Read, false);
} }
@ -54,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
return guidMapper.OldGuid(newId); return guidMapper.OldGuid(newId);
} }
public async Task<JToken> ReadJsonAttachmentAsync(string name) public Task<T> ReadJsonAttachmentAsync<T>(string name)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
@ -65,24 +73,16 @@ namespace Squidex.Domain.Apps.Entities.Backup
throw new FileNotFoundException("Cannot find attachment.", name); throw new FileNotFoundException("Cannot find attachment.", name);
} }
JToken result; T result;
using (var stream = attachmentEntry.Open()) using (var stream = attachmentEntry.Open())
{ {
using (var textReader = new StreamReader(stream)) result = serializer.Deserialize<T>(stream, null, guidMapper.NewGuidOrValue);
{
using (var jsonReader = new JsonTextReader(textReader))
{
result = await JToken.ReadFromAsync(jsonReader);
guidMapper.NewGuids(result);
}
}
} }
readAttachments++; readAttachments++;
return result; return Task.FromResult(result);
} }
public async Task ReadBlobAsync(string name, Func<Stream, Task> handler) public async Task ReadBlobAsync(string name, Func<Stream, Task> handler)
@ -105,9 +105,10 @@ namespace Squidex.Domain.Apps.Entities.Backup
readAttachments++; 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(handler, nameof(handler));
Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
while (true) while (true)
@ -121,29 +122,34 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = eventEntry.Open()) using (var stream = eventEntry.Open())
{ {
using (var textReader = new StreamReader(stream)) var (streamName, data) = serializer.Deserialize<CompatibleStoredEvent>(stream).ToEvent();
{
using (var jsonReader = new JsonTextReader(textReader))
{
var storedEvent = Serializer.Deserialize<StoredEvent>(jsonReader);
storedEvent.Data.Payload = guidMapper.NewGuids(storedEvent.Data.Payload); MapHeaders(data);
storedEvent.Data.Metadata = guidMapper.NewGuids(storedEvent.Data.Metadata);
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, await handler((eventStream, eventEnvelope));
storedEvent.EventPosition,
storedEvent.EventStreamNumber,
storedEvent.Data);
await handler(storedEvent);
}
}
} }
readEvents++; 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;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Backup.Helpers; using Squidex.Domain.Apps.Entities.Backup.Helpers;
using Squidex.Domain.Apps.Entities.Backup.Model;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
public sealed class BackupWriter : DisposableObjectBase public sealed class BackupWriter : DisposableObjectBase
{ {
private static readonly JsonSerializer Serializer = new JsonSerializer();
private readonly ZipArchive archive; private readonly ZipArchive archive;
private readonly IJsonSerializer serializer;
private readonly Func<StoredEvent, CompatibleStoredEvent> converter;
private int writtenEvents; private int writtenEvents;
private int writtenAttachments; private int writtenAttachments;
@ -34,8 +36,17 @@ namespace Squidex.Domain.Apps.Entities.Backup
get { return writtenAttachments; } 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); 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)); Guard.NotNullOrEmpty(name, nameof(name));
@ -55,16 +66,12 @@ namespace Squidex.Domain.Apps.Entities.Backup
using (var stream = attachmentEntry.Open()) using (var stream = attachmentEntry.Open())
{ {
using (var textWriter = new StreamWriter(stream)) serializer.Serialize(value, stream);
{
using (var jsonWriter = new JsonTextWriter(textWriter))
{
await value.WriteToAsync(jsonWriter);
}
}
} }
writtenAttachments++; writtenAttachments++;
return TaskHelper.Done;
} }
public async Task WriteBlobAsync(string name, Func<Stream, Task> handler) 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 stream = eventEntry.Open())
{ {
using (var textWriter = new StreamWriter(stream)) var @event = converter(storedEvent);
{
using (var jsonWriter = new JsonTextWriter(textWriter)) serializer.Serialize(@event, stream);
{
Serializer.Serialize(jsonWriter, storedEvent);
}
}
} }
writtenEvents++; writtenEvents++;

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

@ -7,149 +7,87 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Backup namespace Squidex.Domain.Apps.Entities.Backup
{ {
public sealed class GuidMapper internal sealed class GuidMapper
{ {
private static readonly int GuidLength = Guid.Empty.ToString().Length; 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> oldToNewGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<Guid, Guid> newToOldGuid = new Dictionary<Guid, Guid>(); private readonly Dictionary<Guid, Guid> newToOldGuid = new Dictionary<Guid, Guid>();
private readonly Dictionary<string, string> strings = new Dictionary<string, string>();
public Guid NewGuid(Guid oldGuid)
{
return oldToNewGuid.GetOrDefault(oldGuid);
}
public Guid OldGuid(Guid newGuid) 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; return null;
} }
public JToken NewGuids(JToken jToken) public string NewGuidOrValue(string value)
{
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)
{ {
switch (jToken.Type) if (TryGenerateNewGuidString(value, out var result) || TryGenerateNewNamedId(value, out result))
{ {
case JTokenType.String: return result;
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 jToken; return value;
}
private void NewGuidsCore(JArray jArray)
{
for (var i = 0; i < jArray.Count; i++)
{
jArray[i] = NewGuidsCore(jArray[i]);
}
} }
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 (strings.TryGetValue(value, out result))
if (!ReferenceEquals(newValue, jProperty.Value))
{
jProperty.Value = newValue;
}
if (TryConvertString(jProperty.Name, out var newKey))
{ {
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)) if (Guid.TryParse(value, out var guid))
{ {
var newGuid = GenerateNewGuid(guid); var newGuid = GenerateNewGuid(guid);
result = newGuid.ToString(); strings[value] = result = newGuid.ToString();
return true; return true;
} }
} }
result = null;
return false; return false;
} }
private bool TryGenerateNewNamedId(string value, out string result) private bool TryGenerateNewNamedId(string value, out string result)
{ {
result = null; if (value.Length > GuidLength)
if (value.Length > GuidLength && value[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; return true;
} }
} }
result = null;
return false; return false;
} }

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

@ -9,6 +9,7 @@ using System;
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.Backup.Helpers 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; Stream stream = null;
@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers
{ {
stream = await backupArchiveLocation.OpenStreamAsync(id); stream = await backupArchiveLocation.OpenStreamAsync(id);
return new BackupReader(stream); return new BackupReader(serializer, stream);
} }
catch (IOException) 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;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -31,6 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IClock clock; private readonly IClock clock;
private readonly ICommandBus commandBus; private readonly ICommandBus commandBus;
private readonly IEnumerable<BackupHandler> handlers; private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers, IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log, ISemanticLog log,
IStreamNameResolver streamNameResolver, IStreamNameResolver streamNameResolver,
IStore<string> store) IStore<string> store)
@ -61,6 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers)); Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store)); Guard.NotNull(store, nameof(store));
Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver));
Guard.NotNull(log, nameof(log)); Guard.NotNull(log, nameof(log));
@ -71,6 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers; this.handlers = handlers;
this.serializer = serializer;
this.store = store; this.store = store;
this.streamNameResolver = streamNameResolver; this.streamNameResolver = streamNameResolver;
this.log = log; this.log = log;
@ -161,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
await DownloadAsync(); await DownloadAsync();
} }
using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id)) using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id, serializer))
{ {
using (Profiler.Trace("ReadEvents")) using (Profiler.Trace("ReadEvents"))
{ {
@ -189,6 +194,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
} }
} }
await AssignContributorAsync();
CurrentJob.Status = JobStatus.Completed; CurrentJob.Status = JobStatus.Completed;
Log("Completed, Yeah!"); Log("Completed, Yeah!");
@ -247,6 +254,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
FromRestore = true, FromRestore = true,
Role = Role.Owner Role = Role.Owner
}); });
Log("Assigned current user.");
} }
private async Task CleanupAsync() private async Task CleanupAsync()
@ -273,17 +282,15 @@ namespace Squidex.Domain.Apps.Entities.Backup
private async Task ReadEventsAsync(BackupReader reader) 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.Stream, storedEvent.Event);
await HandleEventAsync(reader, 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) 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 eventData = eventDataFormatter.ToEventData(@event, @event.Headers.CommitId());
var eventCommit = new List<EventData> { eventData }; 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); 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 System.Collections.Generic;
using Newtonsoft.Json; using System.Runtime.Serialization;
namespace Squidex.Domain.Apps.Entities.Backup.State namespace Squidex.Domain.Apps.Entities.Backup.State
{ {
public sealed class BackupState public sealed class BackupState
{ {
[JsonProperty] [DataMember]
public List<BackupStateJob> Jobs { get; } = new List<BackupStateJob>(); 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 System;
using Newtonsoft.Json; using System.Runtime.Serialization;
using NodaTime; using NodaTime;
namespace Squidex.Domain.Apps.Entities.Backup.State namespace Squidex.Domain.Apps.Entities.Backup.State
{ {
public sealed class BackupStateJob : IBackupJob public sealed class BackupStateJob : IBackupJob
{ {
[JsonProperty] [DataMember]
public Guid Id { get; set; } public Guid Id { get; set; }
[JsonProperty] [DataMember]
public Instant Started { get; set; } public Instant Started { get; set; }
[JsonProperty] [DataMember]
public Instant? Stopped { get; set; } public Instant? Stopped { get; set; }
[JsonProperty] [DataMember]
public int HandledEvents { get; set; } public int HandledEvents { get; set; }
[JsonProperty] [DataMember]
public int HandledAssets { get; set; } public int HandledAssets { get; set; }
[JsonProperty] [DataMember]
public JobStatus Status { get; set; } 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json; using System.Runtime.Serialization;
namespace Squidex.Domain.Apps.Entities.Backup.State namespace Squidex.Domain.Apps.Entities.Backup.State
{ {
public class RestoreState public class RestoreState
{ {
[JsonProperty] [DataMember]
public RestoreStateJob Job { get; set; } public RestoreStateJob Job { get; set; }
} }
} }

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

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

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

@ -8,8 +8,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public sealed class GraphQLExecutionContext : QueryExecutionContext public sealed class GraphQLExecutionContext : QueryExecutionContext
@ -25,29 +26,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
UrlGenerator = urlGenerator; UrlGenerator = urlGenerator;
} }
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(JToken value) public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(IJsonValue value)
{ {
var ids = ParseIds(value); var ids = ParseIds(value);
return GetReferencedAssetsAsync(ids); 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); var ids = ParseIds(value);
return GetReferencedContentsAsync(schemaId, ids); return GetReferencedContentsAsync(schemaId, ids);
} }
private static ICollection<Guid> ParseIds(JToken value) private static ICollection<Guid> ParseIds(IJsonValue value)
{ {
try try
{ {
var result = new List<Guid>(); 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())); 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 Guid = new GuidGraphType2();
public static readonly IGraphType Date = new DateTimeGraphType(); public static readonly IGraphType Date = new InstantGraphType();
public static readonly IGraphType Json = new JsonGraphType(); 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", Name = "created",
ResolvedType = AllTypes.NonNullDate, 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." 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", Name = "lastModified",
ResolvedType = AllTypes.NonNullDate, 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." 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 System.Linq;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types 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."; 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); 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", Name = "created",
ResolvedType = AllTypes.NonNullDate, 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." 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", Name = "lastModified",
ResolvedType = AllTypes.NonNullDate, 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." 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 System.Linq;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types 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) 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 => 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); return fieldInfo.Resolver(value, c);
} }

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

@ -7,13 +7,13 @@
using System; using System;
using GraphQL.Types; using GraphQL.Types;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types 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)> 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.Language.AST;
using GraphQL.Types; using GraphQL.Types;
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils 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) 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) 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 GraphQL.Language.AST;
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils 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; Value = value;
} }
protected override bool Equals(ValueNode<JObject> node) protected override bool Equals(ValueNode<IJsonValue> node)
{ {
return false; return false;
} }

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

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

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

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

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

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

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

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

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

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Globalization;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events namespace Squidex.Domain.Apps.Events
@ -15,12 +14,12 @@ namespace Squidex.Domain.Apps.Events
{ {
public static Guid AppId(this EnvelopeHeaders headers) 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 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; return envelope;
} }

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

@ -8,20 +8,23 @@
using System; using System;
using System.Text; using System.Text;
using EventStore.ClientAPI; using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using EventStoreData = EventStore.ClientAPI.EventData; using EventStoreData = EventStore.ClientAPI.EventData;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public static class Formatter public static class Formatter
{ {
public static StoredEvent Read(ResolvedEvent resolvedEvent) public static StoredEvent Read(ResolvedEvent resolvedEvent, IJsonSerializer serializer)
{ {
var @event = resolvedEvent.Event; var @event = resolvedEvent.Event;
var body = Encoding.UTF8.GetString(@event.Data); var metadata = Encoding.UTF8.GetString(@event.Data);
var meta = Encoding.UTF8.GetString(@event.Metadata);
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( return new StoredEvent(
@event.EventStreamId, @event.EventStreamId,
@ -30,12 +33,14 @@ namespace Squidex.Infrastructure.EventSourcing
eventData); eventData);
} }
public static EventStoreData Write(EventData eventData) public static EventStoreData Write(EventData eventData, IJsonSerializer serializer)
{ {
var body = Encoding.UTF8.GetBytes(eventData.Payload.ToString()); var payload = Encoding.UTF8.GetBytes(eventData.Payload);
var meta = Encoding.UTF8.GetBytes(eventData.Metadata.ToString());
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;
using System.Threading.Tasks; using System.Threading.Tasks;
using EventStore.ClientAPI; using EventStore.ClientAPI;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
@ -20,14 +21,17 @@ namespace Squidex.Infrastructure.EventSourcing
private const int WritePageSize = 500; private const int WritePageSize = 500;
private const int ReadPageSize = 500; private const int ReadPageSize = 500;
private readonly IEventStoreConnection connection; private readonly IEventStoreConnection connection;
private readonly IJsonSerializer serializer;
private readonly string prefix; private readonly string prefix;
private readonly ProjectionClient projectionClient; 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(connection, nameof(connection));
Guard.NotNull(serializer, nameof(serializer));
this.connection = connection; this.connection = connection;
this.serializer = serializer;
this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex"); this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex");
@ -50,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing
public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null) 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) public Task CreateIndexAsync(string property)
@ -95,7 +99,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events) foreach (var resolved in currentSlice.Events)
{ {
var storedEvent = Formatter.Read(resolved); var storedEvent = Formatter.Read(resolved, serializer);
await callback(storedEvent); await callback(storedEvent);
} }
@ -123,7 +127,7 @@ namespace Squidex.Infrastructure.EventSourcing
foreach (var resolved in currentSlice.Events) foreach (var resolved in currentSlice.Events)
{ {
var storedEvent = Formatter.Read(resolved); var storedEvent = Formatter.Read(resolved, serializer);
result.Add(storedEvent); result.Add(storedEvent);
} }
@ -164,7 +168,7 @@ namespace Squidex.Infrastructure.EventSourcing
return; return;
} }
var eventsToSave = events.Select(Formatter.Write).ToList(); var eventsToSave = events.Select(x => Formatter.Write(x, serializer)).ToList();
if (eventsToSave.Count < WritePageSize) if (eventsToSave.Count < WritePageSize)
{ {

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

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

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

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
@ -21,18 +21,19 @@ namespace Squidex.Infrastructure.EventSourcing
[BsonRequired] [BsonRequired]
public string Payload { get; set; } public string Payload { get; set; }
[BsonElement] [BsonElement("Metadata")]
[BsonRequired] [BsonRequired]
public JToken Metadata { get; set; } [BsonJson]
public EnvelopeHeaders Headers { get; set; }
public static MongoEvent FromEventData(EventData data) 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() 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()); 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) else if (bsonReader.State == BsonReaderState.Value)
{ {
switch (bsonReader.CurrentBsonType) switch (bsonReader.CurrentBsonType)
@ -95,6 +85,16 @@ namespace Squidex.Infrastructure.MongoDb
throw new NotSupportedException(); 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) 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)); var update = updater(Builders<T>.Update.Set(x => x.Version, newVersion));
await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, update, Upsert);
update
.Set(x => x.Version, newVersion),
Upsert);
} }
catch (MongoWriteException ex) catch (MongoWriteException ex)
{ {

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

@ -9,16 +9,16 @@ using System;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using RabbitMQ.Client; using RabbitMQ.Client;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Events namespace Squidex.Infrastructure.CQRS.Events
{ {
public sealed class RabbitMqEventConsumer : DisposableObjectBase, IInitializable, IEventConsumer public sealed class RabbitMqEventConsumer : DisposableObjectBase, IInitializable, IEventConsumer
{ {
private readonly JsonSerializerSettings serializerSettings; private readonly IJsonSerializer jsonSerializer;
private readonly string eventPublisherName; private readonly string eventPublisherName;
private readonly string exchange; private readonly string exchange;
private readonly string eventsFilter; private readonly string eventsFilter;
@ -36,12 +36,12 @@ namespace Squidex.Infrastructure.CQRS.Events
get { return eventsFilter; } 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(uri, nameof(uri));
Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName)); Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName));
Guard.NotNullOrEmpty(exchange, nameof(exchange)); Guard.NotNullOrEmpty(exchange, nameof(exchange));
Guard.NotNull(serializerSettings, nameof(serializerSettings)); Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
connectionFactory = new ConnectionFactory { Uri = new Uri(uri, UriKind.Absolute) }; connectionFactory = new ConnectionFactory { Uri = new Uri(uri, UriKind.Absolute) };
connection = new Lazy<IConnection>(connectionFactory.CreateConnection); connection = new Lazy<IConnection>(connectionFactory.CreateConnection);
@ -49,8 +49,8 @@ namespace Squidex.Infrastructure.CQRS.Events
this.exchange = exchange; this.exchange = exchange;
this.eventsFilter = eventsFilter; this.eventsFilter = eventsFilter;
this.jsonSerializer = jsonSerializer;
this.eventPublisherName = eventPublisherName; this.eventPublisherName = eventPublisherName;
this.serializerSettings = serializerSettings;
} }
protected override void DisposeObject(bool disposing) protected override void DisposeObject(bool disposing)
@ -88,7 +88,7 @@ namespace Squidex.Infrastructure.CQRS.Events
public Task On(Envelope<IEvent> @event) public Task On(Envelope<IEvent> @event)
{ {
var jsonString = JsonConvert.SerializeObject(@event, serializerSettings); var jsonString = jsonSerializer.Serialize(@event);
var jsonBytes = Encoding.UTF8.GetBytes(jsonString); var jsonBytes = Encoding.UTF8.GetBytes(jsonString);
channel.Value.BasicPublish(exchange, string.Empty, null, jsonBytes); 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.Collections.Concurrent;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
using StackExchange.Redis; using StackExchange.Redis;
@ -19,11 +20,13 @@ namespace Squidex.Infrastructure
{ {
private readonly ConcurrentDictionary<string, object> subscriptions = new ConcurrentDictionary<string, object>(); private readonly ConcurrentDictionary<string, object> subscriptions = new ConcurrentDictionary<string, object>();
private readonly Lazy<IConnectionMultiplexer> redisClient; private readonly Lazy<IConnectionMultiplexer> redisClient;
private readonly IJsonSerializer serializer;
private readonly Lazy<ISubscriber> redisSubscriber; private readonly Lazy<ISubscriber> redisSubscriber;
private readonly ISemanticLog log; 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(redis, nameof(redis));
Guard.NotNull(log, nameof(log)); Guard.NotNull(log, nameof(log));
@ -31,6 +34,8 @@ namespace Squidex.Infrastructure
redisClient = redis; redisClient = redis;
redisSubscriber = new Lazy<ISubscriber>(() => redis.Value.GetSubscriber()); redisSubscriber = new Lazy<ISubscriber>(() => redis.Value.GetSubscriber());
this.serializer = serializer;
} }
public Task InitializeAsync(CancellationToken ct = default(CancellationToken)) public Task InitializeAsync(CancellationToken ct = default(CancellationToken))
@ -61,7 +66,7 @@ namespace Squidex.Infrastructure
{ {
var typeName = typeof(T).FullName; 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;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Newtonsoft.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using StackExchange.Redis; using StackExchange.Redis;
@ -20,6 +20,7 @@ namespace Squidex.Infrastructure
private readonly Guid selfId = Guid.NewGuid(); private readonly Guid selfId = Guid.NewGuid();
private readonly Subject<T> subject = new Subject<T>(); private readonly Subject<T> subject = new Subject<T>();
private readonly ISubscriber subscriber; private readonly ISubscriber subscriber;
private readonly IJsonSerializer serializer;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private readonly string channelName; private readonly string channelName;
@ -30,10 +31,11 @@ namespace Squidex.Infrastructure
public Guid Sender; 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.log = log;
this.serializer = serializer;
this.subscriber = subscriber; this.subscriber = subscriber;
this.subscriber.Subscribe(channelName, (channel, value) => HandleMessage(value)); this.subscriber.Subscribe(channelName, (channel, value) => HandleMessage(value));
@ -46,7 +48,7 @@ namespace Squidex.Infrastructure
{ {
var senderId = notifySelf ? Guid.Empty : selfId; 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); subscriber.Publish(channelName, envelope);
} }
@ -68,7 +70,7 @@ namespace Squidex.Infrastructure
return; return;
} }
var envelope = JsonConvert.DeserializeObject<Envelope>(value); var envelope = serializer.Deserialize<Envelope>(value);
if (envelope.Sender != selfId) if (envelope.Sender != selfId)
{ {

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

@ -7,7 +7,6 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Assets namespace Squidex.Infrastructure.Assets
{ {
@ -21,7 +20,6 @@ namespace Squidex.Infrastructure.Assets
public long FileSize { get; } public long FileSize { get; }
[JsonConstructor]
public AssetFile(string fileName, string mimeType, long fileSize, Func<Stream> openAction) public AssetFile(string fileName, string mimeType, long fileSize, Func<Stream> openAction)
{ {
Guard.NotNullOrEmpty(fileName, nameof(fileName)); 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); var result = await ExecuteAsync(command.Value);
return result.AsJ(); return result;
} }
protected abstract Task<object> ExecuteAsync(IAggregateCommand command); protected abstract Task<object> ExecuteAsync(IAggregateCommand command);

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

@ -6,38 +6,36 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json; using Squidex.Infrastructure.Json;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.EventSourcing 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; private readonly TypeNameRegistry typeNameRegistry;
public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, JsonSerializer serializer = null) public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer)
{ {
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
Guard.NotNull(serializer, nameof(serializer));
this.typeNameRegistry = typeNameRegistry; 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); if (payloadObj is IMigratedEvent migratedEvent)
var content = eventData.Payload.ToObject(eventType, serializer) as IEvent;
if (migrate && content 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; return envelope;
} }
@ -51,14 +49,12 @@ namespace Squidex.Infrastructure.EventSourcing
eventPayload = migratedEvent.Migrate(); eventPayload = migratedEvent.Migrate();
} }
var eventType = typeNameRegistry.GetName(eventPayload.GetType()); var payloadType = typeNameRegistry.GetName(eventPayload.GetType());
var payloadJson = serializer.Serialize(envelope.Payload);
envelope.SetCommitId(commitId); envelope.SetCommitId(commitId);
var headers = JToken.FromObject(envelope.Headers, serializer); return new EventData(payloadType, envelope.Headers, payloadJson);
var content = JToken.FromObject(envelope.Payload, serializer);
return new EventData { Type = eventType, Payload = content, Metadata = headers };
} }
} }
} }

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

@ -8,6 +8,8 @@
using System; using System;
using System.Globalization; using System.Globalization;
using NodaTime; using NodaTime;
using NodaTime.Text;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
@ -15,74 +17,127 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
public static string EventPosition(this EnvelopeHeaders headers) 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 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; return envelope;
} }
public static long EventStreamNumber(this EnvelopeHeaders headers) 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 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; return envelope;
} }
public static Guid CommitId(this EnvelopeHeaders headers) 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 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; return envelope;
} }
public static Guid AggregateId(this EnvelopeHeaders headers) 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 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; return envelope;
} }
public static Guid EventId(this EnvelopeHeaders headers) 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 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; return envelope;
} }
public static Instant Timestamp(this EnvelopeHeaders headers) 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 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; 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public sealed class EnvelopeHeaders : PropertiesBag public sealed class EnvelopeHeaders : JsonObject
{ {
public EnvelopeHeaders() 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() public EnvelopeHeaders Clone()
{ {
var clone = new EnvelopeHeaders(); return new EnvelopeHeaders(this);
foreach (var property in Properties)
{
clone.Set(property.Key, property.Value.RawValue);
}
return clone;
} }
} }
} }

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

Loading…
Cancel
Save