Browse Source

Refactoring/serializers (#654)

* #639 Create JSON schemas for all rule events

* Serializers simplified.

* Converters removed.

* Get rid of serializer code.

* Json fixes.

* Tests fixed.

* Tests fixed.

* Fix tests?

* Bugfix for references.
pull/656/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
a41e13c6b8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs
  2. 2
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs
  3. 2
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  4. 1
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  5. 2
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  6. 5
      backend/src/Migrations/Migrations/ConvertEventStore.cs
  7. 5
      backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs
  8. 36
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs
  9. 33
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsSurrogate.cs
  10. 36
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs
  11. 33
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsSurrogate.cs
  12. 37
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs
  13. 33
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs
  14. 21
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonRole.cs
  15. 18
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs
  16. 30
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs
  17. 27
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigSurrogate.cs
  18. 72
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RoleConverter.cs
  19. 59
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs
  20. 76
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs
  21. 25
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  22. 39
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/StatusConverter.cs
  23. 30
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepConverter.cs
  24. 31
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs
  25. 20
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs
  26. 37
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowsConverter.cs
  27. 28
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowsSurrogate.cs
  28. 14
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusInfo.cs
  29. 7
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusTypeConverter.cs
  30. 26
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs
  31. 18
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs
  32. 25
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs
  33. 37
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs
  34. 26
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs
  35. 38
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs
  36. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueGenerator.cs
  37. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  38. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  39. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/DateTimeFluidExtension.cs
  40. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs
  41. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  42. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs
  43. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  44. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  45. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  46. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs
  47. 8
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  48. 6
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs
  49. 2
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  50. 12
      backend/src/Squidex.Domain.Apps.Entities/Backup/UserMapping.cs
  51. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs
  52. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs
  53. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs
  54. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs
  55. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  56. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  57. 2
      backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs
  58. 5
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs
  59. 7
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs
  60. 32
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs
  61. 2
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs
  62. 3
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs
  63. 13
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  64. 53
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/JTokenSerializer.cs
  65. 2
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  66. 40
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs
  67. 26
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/TypeConverterStringSerializer.cs
  68. 12
      backend/src/Squidex.Infrastructure/CollectionExtensions.cs
  69. 2
      backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  70. 13
      backend/src/Squidex.Infrastructure/ISurrogate.cs
  71. 78
      backend/src/Squidex.Infrastructure/Json/ClaimsPrinicpalSurrogate.cs
  72. 2
      backend/src/Squidex.Infrastructure/Json/ISupportedTypes.cs
  73. 35
      backend/src/Squidex.Infrastructure/Json/JsonException.cs
  74. 62
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs
  75. 62
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/DomainIdConverter.cs
  76. 69
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs
  77. 27
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs
  78. 41
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedDomainIdConverter.cs
  79. 34
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs
  80. 34
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs
  81. 41
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs
  82. 22
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs
  83. 32
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs
  84. 31
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/SurrogateConverter.cs
  85. 4
      backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
  86. 10
      backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs
  87. 2
      backend/src/Squidex.Infrastructure/Language.cs
  88. 36
      backend/src/Squidex.Infrastructure/LanguageTypeConverter.cs
  89. 83
      backend/src/Squidex.Infrastructure/NamedIdTypeConverter.cs
  90. 165
      backend/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs
  91. 91
      backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs
  92. 29
      backend/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs
  93. 1
      backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs
  94. 2
      backend/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs
  95. 57
      backend/src/Squidex.Infrastructure/RefToken.cs
  96. 7
      backend/src/Squidex.Infrastructure/RefTokenType.cs
  97. 36
      backend/src/Squidex.Infrastructure/RefTokenTypeConverter.cs
  98. 4
      backend/src/Squidex.Infrastructure/Security/Extensions.cs
  99. 2
      backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs
  100. 2
      backend/src/Squidex.Web/ContextExtensions.cs

7
backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs

@ -84,8 +84,9 @@ namespace Squidex.Extensions.Actions.Algolia
json = new JObject(new JProperty("error", $"Invalid JSON: {ex.Message}"));
}
ruleJob.Content = json;
ruleJob.Content["objectID"] = contentId;
json["objectID"] = contentId;
ruleJob.Content = json.ToString();
}
return (ruleDescription, ruleJob);
@ -135,6 +136,6 @@ namespace Squidex.Extensions.Actions.Algolia
public string IndexName { get; set; }
public JObject Content { get; set; }
public string Content { get; set; }
}
}

2
backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs

@ -41,7 +41,7 @@ namespace Squidex.Extensions.Actions.Comment
if (!string.IsNullOrEmpty(action.Client))
{
ruleJob.Actor = new RefToken(RefTokenType.Client, action.Client);
ruleJob.Actor = RefToken.Client(action.Client);
}
else
{

2
backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs

@ -61,7 +61,7 @@ namespace Squidex.Extensions.Actions.CreateContent
if (!string.IsNullOrEmpty(action.Client))
{
ruleJob.Actor = new RefToken(RefTokenType.Client, action.Client);
ruleJob.Actor = RefToken.Client(action.Client);
}
else if (@event is EnrichedUserEventBase userEvent)
{

1
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -17,7 +17,6 @@ using Confluent.Kafka;
using Confluent.SchemaRegistry;
using Confluent.SchemaRegistry.Serdes;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Log;

2
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs

@ -45,7 +45,7 @@ namespace Squidex.Extensions.Actions.Notification
if (!string.IsNullOrEmpty(action.Client))
{
actor = new RefToken(RefTokenType.Client, action.Client);
actor = RefToken.Client(action.Client);
}
var user = await userResolver.FindByIdOrEmailAsync(action.User);

5
backend/src/Migrations/Migrations/ConvertEventStore.cs

@ -9,7 +9,6 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
@ -53,10 +52,10 @@ namespace Migrations.Migrations
{
foreach (BsonDocument @event in commit["Events"].AsBsonArray)
{
var meta = JObject.Parse(@event["Metadata"].AsString);
var meta = BsonDocument.Parse(@event["Metadata"].AsString);
@event.Remove("EventId");
@event["Metadata"] = meta.ToBson();
@event["Metadata"] = meta;
}
await WriteAsync(new ReplaceOneModel<BsonDocument>(filter.Eq("_id", commit["_id"].AsString), commit), false);

5
backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
@ -60,11 +59,11 @@ namespace Migrations.Migrations
foreach (BsonDocument @event in commit["Events"].AsBsonArray)
{
var data = JObject.Parse(@event["Payload"].AsString);
var data = BsonDocument.Parse(@event["Payload"].AsString);
if (data.TryGetValue("appId", out var appIdValue))
{
var appId = NamedId<Guid>.Parse(appIdValue.ToString(), Guid.TryParse).Id.ToString();
var appId = NamedId<Guid>.Parse(appIdValue.AsString, Guid.TryParse).Id.ToString();
var eventUpdate = updater.Set($"Events.{index}.Metadata.AppId", appId);

36
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppClientsConverter : JsonClassConverter<AppClients>
{
protected override void WriteValue(JsonWriter writer, AppClients value, JsonSerializer serializer)
{
var json = new Dictionary<string, AppClient>(value.Count);
foreach (var (key, client) in value)
{
json.Add(key, client);
}
serializer.Serialize(writer, json);
}
protected override AppClients ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<Dictionary<string, AppClient>>(reader)!;
return new AppClients(json);
}
}
}

33
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsSurrogate.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppClientsSurrogate : Dictionary<string, AppClient>, ISurrogate<AppClients>
{
public void FromSource(AppClients source)
{
foreach (var (key, client) in source)
{
Add(key, client);
}
}
public AppClients ToSource()
{
if (Count == 0)
{
return AppClients.Empty;
}
return new AppClients(this);
}
}
}

36
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppContributorsConverter : JsonClassConverter<AppContributors>
{
protected override void WriteValue(JsonWriter writer, AppContributors value, JsonSerializer serializer)
{
var json = new Dictionary<string, string>(value.Count);
foreach (var (userId, role) in value)
{
json.Add(userId, role);
}
serializer.Serialize(writer, json);
}
protected override AppContributors ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<Dictionary<string, string>>(reader)!;
return new AppContributors(json!);
}
}
}

33
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsSurrogate.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppContributorsSurrogate : Dictionary<string, string>, ISurrogate<AppContributors>
{
public void FromSource(AppContributors source)
{
foreach (var (userId, role) in source)
{
Add(userId, role);
}
}
public AppContributors ToSource()
{
if (Count == 0)
{
return AppContributors.Empty;
}
return new AppContributors(this);
}
}
}

37
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs

@ -1,37 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppPatternsConverter : JsonClassConverter<AppPatterns>
{
protected override void WriteValue(JsonWriter writer, AppPatterns value, JsonSerializer serializer)
{
var json = new Dictionary<DomainId, AppPattern>(value.Count);
foreach (var (key, pattern) in value)
{
json.Add(key, pattern);
}
serializer.Serialize(writer, json);
}
protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<Dictionary<DomainId, AppPattern>>(reader)!;
return new AppPatterns(json);
}
}
}

33
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsSurrogate.cs

@ -0,0 +1,33 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class AppPatternsSurrogate : Dictionary<DomainId, AppPattern>, ISurrogate<AppPatterns>
{
public void FromSource(AppPatterns source)
{
foreach (var (key, pattern) in source)
{
Add(key, pattern);
}
}
public AppPatterns ToSource()
{
if (Count == 0)
{
return AppPatterns.Empty;
}
return new AppPatterns(this);
}
}
}

21
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonRole.cs

@ -1,21 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class JsonRole
{
[JsonProperty]
public string[] Permissions { get; set; }
[JsonProperty]
public JsonObject Properties { get; set; }
}
}

18
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs → backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguageConfigSurrogate.cs

@ -6,32 +6,24 @@
// ==========================================================================
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public class JsonLanguageConfig
public sealed class LanguageConfigSurrogate : ISurrogate<LanguageConfig>
{
[JsonProperty]
public Language[]? Fallback { get; set; }
[JsonProperty]
public bool IsOptional { get; set; }
public JsonLanguageConfig()
public void FromSource(LanguageConfig source)
{
}
public JsonLanguageConfig(LanguageConfig config)
{
SimpleMapper.Map(config, this);
IsOptional = source.IsOptional;
Fallback = config.Fallbacks.ToArray();
Fallback = source.Fallbacks.ToArray();
}
public LanguageConfig ToConfig()
public LanguageConfig ToSource()
{
if (!IsOptional && (Fallback == null || Fallback.Length == 0))
{

30
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class LanguagesConfigConverter : JsonClassConverter<LanguagesConfig>
{
protected override void WriteValue(JsonWriter writer, LanguagesConfig value, JsonSerializer serializer)
{
var json = new JsonLanguagesConfig(value);
serializer.Serialize(writer, json);
}
protected override LanguagesConfig ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<JsonLanguagesConfig>(reader)!;
return json.ToConfig();
}
}
}

27
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs → backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigSurrogate.cs

@ -7,32 +7,33 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class JsonLanguagesConfig
public sealed class LanguagesConfigSurrogate : ISurrogate<LanguagesConfig>
{
[JsonProperty]
public Dictionary<string, JsonLanguageConfig> Languages { get; set; }
public Dictionary<string, LanguageConfigSurrogate> Languages { get; set; }
[JsonProperty]
public string Master { get; set; }
public JsonLanguagesConfig()
public void FromSource(LanguagesConfig source)
{
}
public JsonLanguagesConfig(LanguagesConfig value)
Languages = source.Languages.ToDictionary(x => x.Key, source =>
{
Languages = value.Languages.ToDictionary(x => x.Key, x => new JsonLanguageConfig(x.Value));
var surrogate = new LanguageConfigSurrogate();
surrogate.FromSource(source.Value);
return surrogate;
});
Master = value.Master;
Master = source.Master;
}
public LanguagesConfig ToConfig()
public LanguagesConfig ToSource()
{
var languages = Languages.ToDictionary(x => x.Key, x => x.Value.ToConfig());
var languages = Languages.ToDictionary(x => x.Key, x => x.Value.ToSource());
var master = Master ?? languages.Keys.First();

72
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RoleConverter.cs

@ -1,72 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class RoleConverter : JsonClassConverter<JsonRole>
{
protected override void WriteValue(JsonWriter writer, JsonRole value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("permissions");
serializer.Serialize(writer, value.Permissions);
writer.WritePropertyName("properties");
serializer.Serialize(writer, value.Properties);
writer.WriteEndObject();
}
protected override JsonRole ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var permissions = Array.Empty<string>();
var properties = (JsonObject?)null;
if (reader.TokenType == JsonToken.StartArray)
{
permissions = serializer.Deserialize<string[]>(reader)!;
}
else
{
while (reader.Read() && reader.TokenType != JsonToken.EndObject)
{
if (reader.TokenType == JsonToken.PropertyName)
{
var propertyName = reader.Value!.ToString()!;
if (!reader.Read())
{
throw new JsonSerializationException("Unexpected end when reading role.");
}
switch (propertyName.ToLowerInvariant())
{
case "permissions":
permissions = serializer.Deserialize<string[]>(reader)!;
break;
case "properties":
properties = serializer.Deserialize<JsonObject>(reader)!;
break;
}
}
}
}
return new JsonRole
{
Permissions = permissions,
Properties = properties ?? JsonValue.Object()
};
}
}
}

59
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs

@ -1,59 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class RolesConverter : JsonClassConverter<Roles>
{
protected override void WriteValue(JsonWriter writer, Roles value, JsonSerializer serializer)
{
var json = new Dictionary<string, JsonRole>(value.CustomCount);
foreach (var role in value.Custom)
{
json.Add(role.Name, new JsonRole
{
Permissions = role.Permissions.ToIds().ToArray(),
Properties = role.Properties
});
}
serializer.Serialize(writer, json);
}
protected override Roles ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<Dictionary<string, JsonRole>>(reader)!;
if (json.Count == 0)
{
return Roles.Empty;
}
return new Roles(json.ToDictionary(x => x.Key, x =>
{
var (key, value) = x;
var permissions = PermissionSet.Empty;
if (value.Permissions.Length > 0)
{
permissions = new PermissionSet(value.Permissions);
}
return new Role(key, permissions, value.Properties);
}));
}
}
}

76
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs

@ -0,0 +1,76 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
public sealed class RolesSurrogate : Dictionary<string, IJsonValue>, ISurrogate<Roles>
{
public void FromSource(Roles source)
{
foreach (var customRole in source.Custom)
{
var permissions = JsonValue.Array();
foreach (var permission in customRole.Permissions)
{
permissions.Add(JsonValue.Create(permission.Id));
}
var role =
JsonValue.Object()
.Add("permissions", permissions)
.Add("properties", customRole.Properties);
Add(customRole.Name, role);
}
}
public Roles ToSource()
{
if (Count == 0)
{
return Roles.Empty;
}
return new Roles(this.ToDictionary(x => x.Key, x =>
{
var (key, value) = x;
var properties = JsonValue.Object();
var permissions = PermissionSet.Empty;
if (value is JsonArray array)
{
if (array.Count > 0)
{
permissions = new PermissionSet(array.OfType<JsonString>().Select(x => x.ToString()));
}
}
else if (value is JsonObject obj)
{
if (obj.TryGetValue("permissions", out array!) && array.Count > 0)
{
permissions = new PermissionSet(array.OfType<JsonString>().Select(x => x.ToString()));
}
if (!obj.TryGetValue<JsonObject>("properties", out properties))
{
properties = JsonValue.Object();
}
}
return new Role(key, permissions, properties);
}));
}
}
}

25
backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -30,35 +30,18 @@ namespace Squidex.Domain.Apps.Core.Contents
{
}
public ContentFieldData AddValue(object? value)
public ContentFieldData AddInvariant(object? value)
{
return AddJsonValue(JsonValue.Create(value));
}
public ContentFieldData AddValue(string key, object? value)
{
return AddJsonValue(key, JsonValue.Create(value));
}
public ContentFieldData AddJsonValue(IJsonValue value)
{
this[InvariantPartitioning.Key] = value;
this[InvariantPartitioning.Key] = JsonValue.Create(value);
return this;
}
public ContentFieldData AddJsonValue(string key, IJsonValue value)
public ContentFieldData AddLocalized(string key, object? value)
{
Guard.NotNullOrEmpty(key, nameof(key));
if (Language.IsValidLanguage(key))
{
this[key] = value;
}
else
{
this[key] = value;
}
this[key] = JsonValue.Create(value);
return this;
}

39
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/StatusConverter.cs

@ -1,39 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public sealed class StatusConverter : JsonConverter, ISupportedTypes
{
public IEnumerable<Type> SupportedTypes
{
get { yield return typeof(Status); }
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteValue(value!.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
return new Status(value);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Status);
}
}
}

30
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepConverter.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public sealed class WorkflowStepConverter : JsonClassConverter<WorkflowStep>
{
protected override void WriteValue(JsonWriter writer, WorkflowStep value, JsonSerializer serializer)
{
var json = new JsonWorkflowStep(value);
serializer.Serialize(writer, json);
}
protected override WorkflowStep ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<JsonWorkflowStep>(reader)!;
return json.ToStep();
}
}
}

31
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/JsonWorkflowStep.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowStepSurrogate.cs

@ -8,17 +8,14 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public sealed class JsonWorkflowStep
public sealed class WorkflowStepSurrogate : ISurrogate<WorkflowStep>
{
[JsonProperty]
public Dictionary<Status, JsonWorkflowTransition> Transitions { get; set; }
[JsonProperty]
public string? Color { get; set; }
public Dictionary<Status, WorkflowTransitionSurrogate> Transitions { get; set; }
[JsonProperty("noUpdate")]
public bool NoUpdateFlag { get; set; }
@ -26,21 +23,23 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
[JsonProperty("noUpdateRules")]
public NoUpdate? NoUpdate { get; set; }
public JsonWorkflowStep()
public string? Color { get; set; }
public void FromSource(WorkflowStep source)
{
}
SimpleMapper.Map(source, this);
public JsonWorkflowStep(WorkflowStep step)
Transitions = source.Transitions.ToDictionary(x => x.Key, source =>
{
SimpleMapper.Map(step, this);
var surrogate = new WorkflowTransitionSurrogate();
Transitions =
step.Transitions.ToDictionary(
x => x.Key,
x => new JsonWorkflowTransition(x.Value));
surrogate.FromSource(source.Value);
return surrogate;
});
}
public WorkflowStep ToStep()
public WorkflowStep ToSource()
{
var noUpdate = NoUpdate;
@ -52,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
var transitions =
Transitions?.ToDictionary(
x => x.Key,
x => x.Value.ToTransition());
x => x.Value.ToSource());
return new WorkflowStep(transitions, Color, noUpdate);
}

20
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/JsonWorkflowTransition.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowTransitionSurrogate.cs

@ -6,32 +6,26 @@
// ==========================================================================
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public class JsonWorkflowTransition
public sealed class WorkflowTransitionSurrogate : ISurrogate<WorkflowTransition>
{
[JsonProperty]
public string? Expression { get; set; }
[JsonProperty]
public string? Role { get; set; }
[JsonProperty]
public string[]? Roles { get; set; }
public JsonWorkflowTransition()
{
}
public JsonWorkflowTransition(WorkflowTransition transition)
public void FromSource(WorkflowTransition source)
{
Roles = transition.Roles?.ToArray();
Roles = source.Roles?.ToArray();
Expression = transition.Expression;
Expression = source.Expression;
}
public WorkflowTransition ToTransition()
public WorkflowTransition ToSource()
{
var roles = Roles;

37
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowsConverter.cs

@ -1,37 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public sealed class WorkflowsConverter : JsonClassConverter<Workflows>
{
protected override void WriteValue(JsonWriter writer, Workflows value, JsonSerializer serializer)
{
var json = new Dictionary<DomainId, Workflow>(value.Count);
foreach (var (key, workflow) in value)
{
json.Add(key, workflow);
}
serializer.Serialize(writer, json);
}
protected override Workflows ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var json = serializer.Deserialize<Dictionary<DomainId, Workflow>>(reader);
return new Workflows(json!);
}
}
}

28
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowsSurrogate.cs

@ -0,0 +1,28 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public sealed class WorkflowsSurrogate : Dictionary<DomainId, Workflow>, ISurrogate<Workflows>
{
public void FromSource(Workflows source)
{
foreach (var (key, workflow) in source)
{
Add(key, workflow);
}
}
public Workflows ToSource()
{
return new Workflows(this);
}
}
}

14
backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusInfo.cs

@ -5,19 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Core.Contents
{
public sealed class StatusInfo
{
public Status Status { get; }
public string Color { get; }
public StatusInfo(Status status, string color)
public sealed record StatusInfo(Status Status, string Color)
{
Status = status;
Color = color;
}
}
}

7
backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusTypeConverter.cs

@ -25,12 +25,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string s)
{
return new Status(s);
}
return default(Status);
return new Status((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)

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

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Rules.Json
{
public sealed class RuleConverter : JsonClassConverter<Rule>
{
protected override void WriteValue(JsonWriter writer, Rule value, JsonSerializer serializer)
{
serializer.Serialize(writer, new JsonRule(value));
}
protected override Rule ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
return serializer.Deserialize<JsonRule>(reader)!.ToRule();
}
}
}

18
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/JsonRule.cs → backend/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleSorrgate.cs

@ -5,36 +5,28 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Rules.Json
{
public sealed class JsonRule
public sealed class RuleSorrgate : ISurrogate<Rule>
{
[JsonProperty]
public RuleTrigger Trigger { get; set; }
[JsonProperty]
public RuleAction Action { get; set; }
[JsonProperty]
public bool IsEnabled { get; set; }
[JsonProperty]
public string Name { get; set; }
public JsonRule()
public void FromSource(Rule source)
{
SimpleMapper.Map(source, this);
}
public JsonRule(Rule rule)
{
SimpleMapper.Map(rule, this);
}
public Rule ToRule()
public Rule ToSource()
{
var trigger = Trigger;

25
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs → backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/FieldSurrogate.cs

@ -6,45 +6,35 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using P = Squidex.Domain.Apps.Core.Partitioning;
using System.Linq;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonFieldModel : IFieldSettings
public sealed class FieldSurrogate : IFieldSettings
{
[JsonProperty]
public long Id { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public string Partitioning { get; set; }
[JsonProperty]
public bool IsHidden { get; set; }
[JsonProperty]
public bool IsLocked { get; set; }
[JsonProperty]
public bool IsDisabled { get; set; }
[JsonProperty]
public FieldProperties Properties { get; set; }
[JsonProperty]
public JsonNestedFieldModel[]? Children { get; set; }
public FieldSurrogate[]? Children { get; set; }
public RootField ToField()
{
var partitioning = P.FromString(Partitioning);
var partitioning = Core.Partitioning.FromString(Partitioning);
if (Properties is ArrayFieldProperties arrayProperties)
{
var nested = Children?.Map(n => n.ToNestedField()) ?? Array.Empty<NestedField>();
var nested = Children?.Select(n => n.ToNestedField()).ToArray() ?? Array.Empty<NestedField>();
return new ArrayField(Id, Name, partitioning, nested, arrayProperties, this);
}
@ -53,5 +43,10 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
return Properties.CreateRootField(Id, Name, partitioning, this);
}
}
public NestedField ToNestedField()
{
return Properties.CreateNestedField(Id, Name, this);
}
}
}

37
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs

@ -1,37 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonNestedFieldModel : IFieldSettings
{
[JsonProperty]
public long Id { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public bool IsHidden { get; set; }
[JsonProperty]
public bool IsLocked { get; set; }
[JsonProperty]
public bool IsDisabled { get; set; }
[JsonProperty]
public FieldProperties Properties { get; set; }
public NestedField ToNestedField()
{
return Properties.CreateNestedField(Id, Name, this);
}
}
}

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

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class SchemaConverter : JsonClassConverter<Schema>
{
protected override void WriteValue(JsonWriter writer, Schema value, JsonSerializer serializer)
{
serializer.Serialize(writer, new JsonSchemaModel(value));
}
protected override Schema ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
return serializer.Deserialize<JsonSchemaModel>(reader)!.ToSchema();
}
}
}

38
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs → backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaSurrogate.cs

@ -8,58 +8,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonSchemaModel
public sealed class SchemaSurrogate : ISurrogate<Schema>
{
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public string Category { get; set; }
[JsonProperty]
public bool IsSingleton { get; set; }
[JsonProperty]
public bool IsPublished { get; set; }
[JsonProperty]
public SchemaProperties Properties { get; set; }
[JsonProperty]
public SchemaScripts? Scripts { get; set; }
[JsonProperty]
public FieldNames? FieldsInLists { get; set; }
[JsonProperty]
public FieldNames? FieldsInReferences { get; set; }
[JsonProperty]
public FieldRules? FieldRules { get; set; }
[JsonProperty]
public JsonFieldModel[] Fields { get; set; }
public FieldSurrogate[] Fields { get; set; }
[JsonProperty]
public Dictionary<string, string>? PreviewUrls { get; set; }
public JsonSchemaModel()
public void FromSource(Schema source)
{
}
public JsonSchemaModel(Schema schema)
{
SimpleMapper.Map(schema, this);
SimpleMapper.Map(source, this);
Fields =
schema.Fields.Select(x =>
new JsonFieldModel
source.Fields.Select(x =>
new FieldSurrogate
{
Id = x.Id,
Name = x.Name,
@ -71,15 +55,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
Properties = x.RawProperties
}).ToArray();
PreviewUrls = schema.PreviewUrls.ToDictionary(x => x.Key, x => x.Value);
PreviewUrls = source.PreviewUrls.ToDictionary(x => x.Key, x => x.Value);
}
private static JsonNestedFieldModel[]? CreateChildren(IField field)
private static FieldSurrogate[]? CreateChildren(IField field)
{
if (field is ArrayField arrayField)
{
return arrayField.Fields.Select(x =>
new JsonNestedFieldModel
new FieldSurrogate
{
Id = x.Id,
Name = x.Name,
@ -93,9 +77,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
return null;
}
public Schema ToSchema()
public Schema ToSource()
{
var fields = Fields.Map(f => f.ToField()) ?? Array.Empty<RootField>();
var fields = Fields?.Select(f => f.ToField()).ToArray() ?? Array.Empty<RootField>();
var schema = new Schema(Name, fields, Properties, IsPublished, IsSingleton);

2
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueGenerator.cs

@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
if (!fieldData.TryGetValue(partitionKey, out var value) || ShouldApplyDefaultValue(field, value))
{
fieldData.AddJsonValue(partitionKey, defaultValue);
fieldData.AddLocalized(partitionKey, defaultValue);
}
}

11
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -16,6 +16,17 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ContentReferencesExtensions
{
public static HashSet<DomainId> GetReferencedIds(this ContentData source, Schema schema, int referencesPerField = int.MaxValue)
{
Guard.NotNull(schema, nameof(schema));
var ids = new HashSet<DomainId>();
AddReferencedIds(source, schema, ids, referencesPerField);
return ids;
}
public static void AddReferencedIds(this ContentData source, Schema schema, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
{
Guard.NotNull(schema, nameof(schema));

4
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -287,7 +287,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
break;
case "timestamp":
{
var instant = InstantPattern.General.Parse(text);
var instant = InstantPattern.ExtendedIso.Parse(text);
if (instant.Success)
{
@ -299,7 +299,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
case "timestamp_sec":
{
var instant = InstantPattern.General.Parse(text);
var instant = InstantPattern.ExtendedIso.Parse(text);
if (instant.Success)
{

2
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/DateTimeFluidExtension.cs

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
{
var value = stringValue.ToStringValue();
var instant = InstantPattern.General.Parse(value);
var instant = InstantPattern.ExtendedIso.Parse(value);
if (instant.Success)
{

2
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs

@ -45,6 +45,8 @@ namespace Squidex.Domain.Apps.Core.Templates
FluidValue.SetTypeMapping(type, x => new StringValue(x.ToString()));
}
FluidValue.SetTypeMapping<RefTokenType>(x => new StringValue(x.ToString().ToLowerInvariant()));
globalTypes.Register<NamedId<DomainId>>();
globalTypes.Register<NamedId<Guid>>();
globalTypes.Register<NamedId<string>>();

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
if (args.Value.Type == JsonValueType.String)
{
var parseResult = InstantPattern.General.Parse(args.Value.ToString());
var parseResult = InstantPattern.ExtendedIso.Parse(args.Value.ToString());
if (!parseResult.Success)
{

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs

@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{
if (args.Value.Type == JsonValueType.String)
{
var parseResult = InstantPattern.General.Parse(args.Value.ToString());
var parseResult = InstantPattern.ExtendedIso.Parse(args.Value.ToString());
return parseResult.Success;
}

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Hosting;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
@ -26,10 +27,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
private readonly MongoContentCollection collectionAll;
private readonly MongoContentCollection collectionPublished;
private readonly IAppProvider appProvider;
static MongoContentRepository()
{
StatusSerializer.Register();
TypeConverterStringSerializer<Status>.Register();
}
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex)
@ -43,6 +45,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
collectionPublished =
new MongoContentCollection(
"States_Contents_Published3", database, appProvider, useWildcardIndex);
this.appProvider = appProvider;
}
public async Task InitializeAsync(CancellationToken ct = default)

17
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -9,6 +9,7 @@ using System;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
@ -93,9 +94,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private async Task UpsertDraftContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion)
{
var content = CreateContent(value, newVersion);
var content = await CreateContentAsync(value, value.Data, newVersion);
content.Data = value.Data;
content.ScheduledAt = value.ScheduleJob?.DueTime;
content.ScheduleJob = value.ScheduleJob;
content.NewStatus = value.NewStatus;
@ -105,9 +105,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private async Task UpsertPublishedContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion)
{
var content = CreateContent(value, newVersion);
var content = await CreateContentAsync(value, value.CurrentVersion.Data, newVersion);
content.Data = value.CurrentVersion.Data;
content.ScheduledAt = null;
content.ScheduleJob = null;
content.NewStatus = null;
@ -115,15 +114,23 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await collectionPublished.UpsertVersionedAsync(content.DocumentId, oldVersion, content);
}
private static MongoContentEntity CreateContent(ContentDomainObject.State value, long newVersion)
private async Task<MongoContentEntity> CreateContentAsync(ContentDomainObject.State value, ContentData data, long newVersion)
{
var content = SimpleMapper.Map(value, new MongoContentEntity());
content.Data = data;
content.DocumentId = value.UniqueId;
content.IndexedAppId = value.AppId.Id;
content.IndexedSchemaId = value.SchemaId.Id;
content.Version = newVersion;
var schema = await appProvider.GetSchemaAsync(value.AppId.Id, value.SchemaId.Id, true);
if (schema != null)
{
content.ReferencedIds = content.Data.GetReferencedIds(schema.SchemaDef);
}
return content;
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -332,7 +332,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
CreateInitialLanguage()
};
if (command.Actor.IsSubject)
if (command.Actor.IsUser)
{
events.Add(CreateInitialOwner(command.Actor));
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
if (@event.Payload is AppContributorAssigned appContributorAssigned)
{
if (!appContributorAssigned.Actor.IsSubject || !appContributorAssigned.IsAdded)
if (!appContributorAssigned.Actor.IsUser || !appContributorAssigned.IsAdded)
{
return;
}

8
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs

@ -60,10 +60,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
new ContentData()
.AddField("title",
new ContentFieldData()
.AddValue("My first post with Squidex"))
.AddInvariant("My first post with Squidex"))
.AddField("text",
new ContentFieldData()
.AddValue("Just created a blog with Squidex. I love it!")),
.AddInvariant("Just created a blog with Squidex. I love it!")),
Publish = true
});
}
@ -79,10 +79,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
new ContentData()
.AddField("title",
new ContentFieldData()
.AddValue("About Me"))
.AddInvariant("About Me"))
.AddField("text",
new ContentFieldData()
.AddValue("I love Squidex and SciFi!")),
.AddInvariant("I love Squidex and SciFi!")),
Publish = true
});
}

6
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs

@ -64,13 +64,13 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
new ContentData()
.AddField("firstName",
new ContentFieldData()
.AddValue("John"))
.AddInvariant("John"))
.AddField("lastName",
new ContentFieldData()
.AddValue("Doe"))
.AddInvariant("Doe"))
.AddField("profession",
new ContentFieldData()
.AddValue("Software Developer")),
.AddInvariant("Software Developer")),
SchemaId = postsId
});
}

2
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -247,7 +247,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
var actor = CurrentJob.Actor;
if (actor?.IsSubject == true)
if (actor?.IsUser == true)
{
try
{

12
backend/src/Squidex.Domain.Apps.Entities/Backup/UserMapping.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
Guard.NotNull(token, nameof(token));
if (!token.IsSubject)
if (!token.IsUser)
{
return;
}
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (!userMap.ContainsKey(userId))
{
userMap[userId] = new RefToken(RefTokenType.Subject, userId);
userMap[userId] = RefToken.User(userId);
}
}
@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (user != null)
{
userMap[userId] = new RefToken(RefTokenType.Subject, user.Id);
userMap[userId] = RefToken.User(user.Id);
}
}
}
@ -87,13 +87,14 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
Guard.NotNullOrEmpty(userId, nameof(userId));
result = initiator;
if (userMap.TryGetValue(userId, out var mapped))
{
result = mapped;
return true;
}
result = initiator;
return false;
}
@ -101,6 +102,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
Guard.NotNull(token, nameof(token));
result = initiator;
if (token.IsClient)
{
result = token;
@ -113,7 +116,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
return true;
}
result = initiator;
return false;
}
}

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

@ -44,7 +44,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
ValueConverter.Register<JsonNumber, double>(x => x.Value);
ValueConverter.Register<JsonString, string>(x => x.Value);
ValueConverter.Register<JsonString, DateTimeOffset>(x => DateTimeOffset.Parse(x.Value, CultureInfo.InvariantCulture));
ValueConverter.Register<string, DomainId>(DomainId.Create);
ValueConverter.Register<string, Status>(x => new Status(x));
}

8
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs

@ -12,11 +12,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
{
internal static class EntityResolvers
{
public static readonly IFieldResolver Id = Resolve<IEntity>(x => x.Id.ToString());
public static readonly IFieldResolver Id = Resolve<IEntity>(x => x.Id);
public static readonly IFieldResolver Created = Resolve<IEntity>(x => x.Created);
public static readonly IFieldResolver CreatedBy = Resolve<IEntityWithCreatedBy>(x => x.CreatedBy.ToString());
public static readonly IFieldResolver LastModified = Resolve<IEntity>(x => x.LastModified.ToString());
public static readonly IFieldResolver LastModifiedBy = Resolve<IEntityWithLastModifiedBy>(x => x.LastModifiedBy.ToString());
public static readonly IFieldResolver CreatedBy = Resolve<IEntityWithCreatedBy>(x => x.CreatedBy);
public static readonly IFieldResolver LastModified = Resolve<IEntity>(x => x.LastModified);
public static readonly IFieldResolver LastModifiedBy = Resolve<IEntityWithLastModifiedBy>(x => x.LastModifiedBy);
public static readonly IFieldResolver Version = Resolve<IEntityWithVersion>(x => x.Version);
private static IFieldResolver Resolve<TSource>(Func<TSource, object> resolver)

6
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs

@ -11,16 +11,16 @@ using NodaTime.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
{
internal sealed class InstantGraphType : DateGraphType
internal sealed class InstantGraphType : DateTimeGraphType
{
public override object Serialize(object value)
{
return ParseValue(value);
return value;
}
public override object ParseValue(object value)
{
return InstantPattern.General.Parse(value.ToString()!).Value;
return InstantPattern.ExtendedIso.Parse(value.ToString()!).Value;
}
public override object? ParseLiteral(IValue value)

6
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs

@ -32,19 +32,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
IAppProvider appProvider,
IContentEnricher contentEnricher,
IContentRepository contentRepository,
IContentLoader assetLoader,
IContentLoader contentLoader,
ContentQueryParser queryParser)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(assetLoader, nameof(assetLoader));
Guard.NotNull(contentLoader, nameof(contentLoader));
Guard.NotNull(queryParser, nameof(queryParser));
this.appProvider = appProvider;
this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository;
this.contentLoader = assetLoader;
this.contentLoader = contentLoader;
this.queryParser = queryParser;
this.queryParser = queryParser;
}

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -103,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
requestCache.AddDependency(referencedAsset.UniqueId, referencedAsset.Version);
fieldReference.AddJsonValue(partitionKey, array);
fieldReference.AddLocalized(partitionKey, array);
}
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs

@ -100,13 +100,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema));
fieldReference.AddJsonValue(partition, value);
fieldReference.AddLocalized(partition, value);
}
else if (referencedContents.Count > 1)
{
var value = CreateFallback(context, referencedContents);
fieldReference.AddJsonValue(partition, value);
fieldReference.AddLocalized(partition, value);
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs

@ -273,7 +273,7 @@ namespace Squidex.Domain.Apps.Entities.History
private static void SetUser(AppEvent appEvent, PublishDto publishRequest)
{
if (appEvent.Actor.IsSubject)
if (appEvent.Actor.IsUser)
{
publishRequest.CreatorId = appEvent.Actor.Identifier;
}

5
backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs

@ -5,19 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
namespace Squidex.Infrastructure.EventSourcing
{
internal sealed class CosmosDbEvent
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("payload")]
public string Payload { get; set; }
[JsonProperty("header")]
public EnvelopeHeaders Headers { get; set; }
public static CosmosDbEvent FromEventData(EventData data)

7
backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs

@ -6,28 +6,21 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.EventSourcing
{
internal sealed class CosmosDbEventCommit
{
[JsonProperty("id")]
public Guid Id { get; set; }
[JsonProperty("events")]
public CosmosDbEvent[] Events { get; set; }
[JsonProperty("eventStreamOffset")]
public long EventStreamOffset { get; set; }
[JsonProperty("eventsCount")]
public long EventsCount { get; set; }
[JsonProperty("eventStream")]
public string EventStream { get; set; }
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
}
}

32
backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs

@ -11,8 +11,8 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;
using Squidex.Hosting;
using Squidex.Infrastructure.Json;
using Index = Microsoft.Azure.Documents.Index;
namespace Squidex.Infrastructure.EventSourcing
@ -22,47 +22,35 @@ namespace Squidex.Infrastructure.EventSourcing
private readonly DocumentClient documentClient;
private readonly Uri collectionUri;
private readonly Uri databaseUri;
private readonly string masterKey;
private readonly string databaseId;
private readonly JsonSerializerSettings serializerSettings;
public JsonSerializerSettings SerializerSettings
{
get { return serializerSettings; }
}
public IJsonSerializer JsonSerializer { get; }
public string DatabaseId
{
get { return databaseId; }
}
public string DatabaseId { get; }
public string MasterKey
{
get { return masterKey; }
}
public string MasterKey { get; }
public Uri ServiceUri
{
get { return documentClient.ServiceEndpoint; }
}
public CosmosDbEventStore(DocumentClient documentClient, string masterKey, string database, JsonSerializerSettings serializerSettings)
public CosmosDbEventStore(DocumentClient documentClient, string masterKey, string database, IJsonSerializer jsonSerializer)
{
Guard.NotNull(documentClient, nameof(documentClient));
Guard.NotNull(serializerSettings, nameof(serializerSettings));
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNullOrEmpty(masterKey, nameof(masterKey));
Guard.NotNullOrEmpty(database, nameof(database));
this.documentClient = documentClient;
databaseUri = UriFactory.CreateDatabaseUri(database);
databaseId = database;
DatabaseId = database;
collectionUri = UriFactory.CreateDocumentCollectionUri(database, Constants.Collection);
this.masterKey = masterKey;
MasterKey = masterKey;
this.serializerSettings = serializerSettings;
JsonSerializer = jsonSerializer;
}
protected override void DisposeObject(bool disposing)
@ -75,7 +63,7 @@ namespace Squidex.Infrastructure.EventSourcing
public async Task InitializeAsync(CancellationToken ct = default)
{
await documentClient.CreateDatabaseIfNotExistsAsync(new Database { Id = databaseId });
await documentClient.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId });
await documentClient.CreateDocumentCollectionIfNotExistsAsync(databaseUri,
new DocumentCollection

2
backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs

@ -36,7 +36,7 @@ namespace Squidex.Infrastructure.EventSourcing
return documentClient.QueryAsync(collectionUri, query, commit =>
{
var documentUri = UriFactory.CreateDocumentUri(databaseId, Constants.Collection, commit.Id.ToString());
var documentUri = UriFactory.CreateDocumentUri(DatabaseId, Constants.Collection, commit.Id.ToString());
return documentClient.DeleteDocumentAsync(documentUri, deleteOptions);
});

3
backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs

@ -12,7 +12,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.ChangeFeedProcessor.FeedProcessing;
using Newtonsoft.Json;
using Builder = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorBuilder;
using Collection = Microsoft.Azure.Documents.ChangeFeedProcessor.DocumentCollectionInfo;
using Options = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorOptions;
@ -117,7 +116,7 @@ namespace Squidex.Infrastructure.EventSourcing
if (regex == null || regex.IsMatch(streamName))
{
var commit = JsonConvert.DeserializeObject<CosmosDbEventCommit>(document.ToString(), store.SerializerSettings)!;
var commit = store.JsonSerializer.Deserialize<CosmosDbEventCommit>(document.ToString());
var eventStreamOffset = (int)commit.EventStreamOffset;

13
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs

@ -12,7 +12,6 @@ using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.MongoDb
{
@ -35,18 +34,6 @@ namespace Squidex.Infrastructure.MongoDb
memberMap.SetSerializer((IBsonSerializer)bsonSerializer!);
}
else if (memberMap.MemberType == typeof(JToken))
{
memberMap.SetSerializer(JTokenSerializer<JToken>.Instance);
}
else if (memberMap.MemberType == typeof(JObject))
{
memberMap.SetSerializer(JTokenSerializer<JObject>.Instance);
}
else if (memberMap.MemberType == typeof(JValue))
{
memberMap.SetSerializer(JTokenSerializer<JValue>.Instance);
}
});
ConventionRegistry.Register("json", pack, t => true);

53
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/JTokenSerializer.cs

@ -1,53 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.MongoDb
{
public sealed class JTokenSerializer<T> : ClassSerializerBase<T?> where T : JToken
{
public static readonly JTokenSerializer<T> Instance = new JTokenSerializer<T>();
public override T? Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonReader = context.Reader;
if (bsonReader.GetCurrentBsonType() == BsonType.Null)
{
bsonReader.ReadNull();
return null;
}
else
{
var jsonReader = new BsonJsonReader(bsonReader);
return (T)JToken.ReadFrom(jsonReader);
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T? value)
{
var bsonWriter = context.Writer;
if (value == null)
{
bsonWriter.WriteNull();
}
else
{
var jsonWriter = new BsonJsonWriter(bsonWriter);
value.WriteTo(jsonWriter);
}
}
}
}

2
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs

@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.MongoDb
static MongoRepositoryBase()
{
RefTokenSerializer.Register();
TypeConverterStringSerializer<RefToken>.Register();
InstantSerializer.Register();

40
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
namespace Squidex.Infrastructure.MongoDb
{
public class RefTokenSerializer : ClassSerializerBase<RefToken>
{
public static void Register()
{
try
{
BsonSerializer.RegisterSerializer(new RefTokenSerializer());
}
catch (BsonSerializationException)
{
return;
}
}
protected override RefToken DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var value = context.Reader.ReadString();
return RefToken.Parse(value);
}
protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, RefToken value)
{
context.Writer.WriteString(value.ToString());
}
}
}

26
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs → backend/src/Squidex.Infrastructure.MongoDb/MongoDb/TypeConverterStringSerializer.cs

@ -5,44 +5,44 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
namespace Squidex.Infrastructure.MongoDb
{
public sealed class StatusSerializer : SerializerBase<Status>
public sealed class TypeConverterStringSerializer<T> : SerializerBase<T>
{
private readonly TypeConverter typeConverter;
public static void Register()
{
try
{
try
{
BsonSerializer.RegisterSerializer(new StatusSerializer());
BsonSerializer.RegisterSerializer(new TypeConverterStringSerializer<T>());
}
catch (BsonSerializationException)
{
return;
}
}
catch (BsonSerializationException)
public TypeConverterStringSerializer()
{
return;
}
typeConverter = TypeDescriptor.GetConverter(typeof(T));
}
public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
public override T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var value = context.Reader.ReadString();
return new Status(value);
return (T)typeConverter.ConvertFromInvariantString(value);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Status value)
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value)
{
context.Writer.WriteString(value.Name);
context.Writer.WriteString(value!.ToString());
}
}
}

12
backend/src/Squidex.Infrastructure/CollectionExtensions.cs

@ -150,18 +150,6 @@ namespace Squidex.Infrastructure
return source.Concat(Enumerable.Repeat(value, 1));
}
public static TResult[] Map<TResult, T>(this T[] value, Func<T, TResult> convert)
{
var result = new TResult[value.Length];
for (var i = 0; i < value.Length; i++)
{
result[i] = convert(value[i]);
}
return result;
}
public static int SequentialHashCode<T>(this IEnumerable<T> collection)
{
return collection.SequentialHashCode(EqualityComparer<T>.Default);

2
backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs

@ -121,7 +121,7 @@ namespace Squidex.Infrastructure.EventSourcing
{
if (obj.TryGetValue(key, out var v))
{
if (v.Type == JsonValueType.String && InstantPattern.General.Parse(v.ToString()).TryGetValue(default, out var instant))
if (v.Type == JsonValueType.String && InstantPattern.ExtendedIso.Parse(v.ToString()).TryGetValue(default, out var instant))
{
return instant;
}

13
backend/src/Squidex.Infrastructure/Json/Newtonsoft/DateConverter.cs → backend/src/Squidex.Infrastructure/ISurrogate.cs

@ -5,15 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Converters;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class DateConverter : IsoDateTimeConverter
namespace Squidex.Infrastructure
{
public DateConverter()
public interface ISurrogate<T>
{
DateTimeFormat = "yyyy-MM-dd";
}
void FromSource(T source);
T ToSource();
}
}

78
backend/src/Squidex.Infrastructure/Json/ClaimsPrinicpalSurrogate.cs

@ -0,0 +1,78 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
namespace Squidex.Infrastructure.Json
{
public sealed class ClaimsPrinicpalSurrogate : List<ClaimsIdentitySurrogate>, ISurrogate<ClaimsPrincipal>
{
public void FromSource(ClaimsPrincipal source)
{
foreach (var identity in source.Identities)
{
var surrogate = new ClaimsIdentitySurrogate();
surrogate.FromSource(identity);
Add(surrogate);
}
}
public ClaimsPrincipal ToSource()
{
return new ClaimsPrincipal(this.Select(x => x.ToSource()));
}
}
public sealed class ClaimsIdentitySurrogate : ISurrogate<ClaimsIdentity>
{
public string? AuthenticationType { get; set; }
public ClaimSurrogate[] Claims { get; set; }
public void FromSource(ClaimsIdentity source)
{
AuthenticationType = source.AuthenticationType;
Claims = source.Claims.Select(claim =>
{
var surrogate = new ClaimSurrogate();
surrogate.FromSource(claim);
return surrogate;
}).ToArray();
}
public ClaimsIdentity ToSource()
{
return new ClaimsIdentity(Claims.Select(x => x.ToSource()), AuthenticationType);
}
}
public sealed class ClaimSurrogate : ISurrogate<Claim>
{
public string Type { get; set; }
public string Value { get; set; }
public void FromSource(Claim source)
{
Type = source.Type;
Value = source.Value;
}
public Claim ToSource()
{
return new Claim(Type, Value);
}
}
}

2
backend/src/Squidex.Infrastructure/Json/Newtonsoft/ISupportedTypes.cs → backend/src/Squidex.Infrastructure/Json/ISupportedTypes.cs

@ -8,7 +8,7 @@
using System;
using System.Collections.Generic;
namespace Squidex.Infrastructure.Json.Newtonsoft
namespace Squidex.Infrastructure.Json
{
public interface ISupportedTypes
{

35
backend/src/Squidex.Infrastructure/Json/JsonException.cs

@ -0,0 +1,35 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Runtime.Serialization;
namespace Squidex.Infrastructure.Json
{
[Serializable]
public class JsonException : Exception
{
public JsonException()
{
}
public JsonException(string message)
: base(message)
{
}
public JsonException(string message, Exception inner)
: base(message, inner)
{
}
protected JsonException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

62
backend/src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs

@ -1,62 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Security.Claims;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class ClaimsPrincipalConverter : JsonClassConverter<ClaimsPrincipal>
{
private sealed class JsonIdentity
{
[JsonProperty]
public string AuthenticationType { get; set; }
[JsonProperty]
public JsonClaim[] Claims { get; set; }
}
private sealed class JsonClaim
{
[JsonProperty]
public string Type { get; set; }
[JsonProperty]
public string Value { get; set; }
}
protected override void WriteValue(JsonWriter writer, ClaimsPrincipal value, JsonSerializer serializer)
{
var jsonIdentities =
value.Identities.Select(identity =>
new JsonIdentity
{
Claims = identity.Claims.Select(c =>
{
return new JsonClaim { Type = c.Type, Value = c.Value };
}).ToArray(),
AuthenticationType = identity.AuthenticationType!
}).ToArray();
serializer.Serialize(writer, jsonIdentities);
}
protected override ClaimsPrincipal ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var jsonIdentities = serializer.Deserialize<JsonIdentity[]>(reader)!;
return new ClaimsPrincipal(
jsonIdentities.Select(identity =>
new ClaimsIdentity(
identity.Claims.Select(c => new Claim(c.Type, c.Value)),
identity.AuthenticationType)));
}
}
}

62
backend/src/Squidex.Infrastructure/Json/Newtonsoft/DomainIdConverter.cs

@ -1,62 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class DomainIdConverter : JsonConverter, ISupportedTypes
{
public IEnumerable<Type> SupportedTypes
{
get
{
yield return typeof(DomainId);
yield return typeof(DomainId?);
}
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
writer.WriteValue(value.ToString());
}
else
{
writer.WriteNull();
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
{
return null;
}
if (reader.TokenType == JsonToken.String)
{
return DomainId.Create(reader.Value.ToString()!);
}
if (reader.TokenType == JsonToken.Null && objectType == typeof(DomainId?))
{
return null;
}
throw new JsonException($"Not a valid date time, expected String, but got {reader.TokenType}.");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DomainId) || objectType == typeof(DomainId?);
}
}
}

69
backend/src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs

@ -1,69 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NodaTime;
using NodaTime.Text;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class InstantConverter : JsonConverter, ISupportedTypes
{
public IEnumerable<Type> SupportedTypes
{
get
{
yield return typeof(Instant);
yield return typeof(Instant?);
}
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
writer.WriteValue(value.ToString());
}
else
{
writer.WriteNull();
}
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
{
return null;
}
if (reader.TokenType == JsonToken.String)
{
return InstantPattern.General.Parse(reader.Value.ToString()!).Value;
}
if (reader.TokenType == JsonToken.Date)
{
return Instant.FromDateTimeUtc((DateTime)reader.Value);
}
if (reader.TokenType == JsonToken.Null && objectType == typeof(Instant?))
{
return null;
}
throw new JsonException($"Not a valid date time, expected String or Date, but got {reader.TokenType}.");
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Instant) || objectType == typeof(Instant?);
}
}
}

27
backend/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs

@ -1,27 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class LanguageConverter : JsonClassConverter<Language>
{
protected override void WriteValue(JsonWriter writer, Language value, JsonSerializer serializer)
{
writer.WriteValue(value.Iso2Code);
}
protected override Language ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
return Language.GetLanguage(value);
}
}
}

41
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedDomainIdConverter.cs

@ -1,41 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class NamedDomainIdConverter : JsonClassConverter<NamedId<DomainId>>
{
private static readonly Parser<DomainId> Parser = ParseString;
protected override void WriteValue(JsonWriter writer, NamedId<DomainId> value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
protected override NamedId<DomainId> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
if (!NamedId<DomainId>.TryParse(value, Parser, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by comma.");
}
return result;
}
private static bool ParseString(ReadOnlySpan<char> value, out DomainId result)
{
result = DomainId.Create(new string(value));
return true;
}
}
}

34
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs

@ -1,34 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class NamedGuidIdConverter : JsonClassConverter<NamedId<Guid>>
{
private static readonly Parser<Guid> Parser = Guid.TryParse;
protected override void WriteValue(JsonWriter writer, NamedId<Guid> value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
protected override NamedId<Guid> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
if (!NamedId<Guid>.TryParse(value, Parser, out var result))
{
throw new JsonException("Named id must have more than 2 parts divided by commata.");
}
return result;
}
}
}

34
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs

@ -1,34 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class NamedLongIdConverter : JsonClassConverter<NamedId<long>>
{
private static readonly Parser<long> Parser = long.TryParse;
protected override void WriteValue(JsonWriter writer, NamedId<long> value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
protected override NamedId<long> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
if (!NamedId<long>.TryParse(value, Parser, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}
return result;
}
}
}

41
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs

@ -1,41 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class NamedStringIdConverter : JsonClassConverter<NamedId<string>>
{
private static readonly Parser<string> Parser = ParseString;
protected override void WriteValue(JsonWriter writer, NamedId<string> value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
protected override NamedId<string> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
if (!NamedId<string>.TryParse(value, Parser, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}
return result;
}
private static bool ParseString(ReadOnlySpan<char> value, out string result)
{
result = new string(value);
return true;
}
}
}

22
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs

@ -8,6 +8,7 @@
using System;
using System.IO;
using Newtonsoft.Json;
using NewtonsoftException = Newtonsoft.Json.JsonException;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
@ -31,6 +32,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
}
public void Serialize<T>(T value, Stream stream, bool leaveOpen = false)
{
try
{
using (var writer = new StreamWriter(stream, leaveOpen: leaveOpen))
{
@ -39,8 +42,15 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
writer.Flush();
}
}
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
}
}
public T Deserialize<T>(string value, Type? actualType = null)
{
try
{
using (var textReader = new StringReader(value))
{
@ -52,8 +62,15 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
}
}
}
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
}
}
public T Deserialize<T>(Stream stream, Type? actualType = null, bool leaveOpen = false)
{
try
{
using (var textReader = new StreamReader(stream, leaveOpen: leaveOpen))
{
@ -65,6 +82,11 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
}
}
}
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
}
}
private static JsonTextReader GetReader(TextReader textReader)
{

32
backend/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs

@ -1,32 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class RefTokenConverter : JsonClassConverter<RefToken>
{
protected override void WriteValue(JsonWriter writer, RefToken value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
protected override RefToken ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
if (!RefToken.TryParse(value, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by colon.");
}
return result;
}
}
}

31
backend/src/Squidex.Infrastructure/Json/Newtonsoft/SurrogateConverter.cs

@ -0,0 +1,31 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class SurrogateConverter<T, TSurrogate> : JsonClassConverter<T> where T : class where TSurrogate : ISurrogate<T>, new()
{
protected override T ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var surrogate = serializer.Deserialize<TSurrogate>(reader);
return surrogate!.ToSource();
}
protected override void WriteValue(JsonWriter writer, T value, JsonSerializer serializer)
{
var surrogate = new TSurrogate();
surrogate.FromSource(value);
serializer.Serialize(writer, surrogate);
}
}
}

4
backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs

@ -35,12 +35,12 @@ namespace Squidex.Infrastructure.Json.Objects
{
}
internal JsonArray(params object?[] values)
internal JsonArray(IEnumerable<object?>? values)
: base(ToList(values))
{
}
private static List<IJsonValue> ToList(IEnumerable<object?> values)
private static List<IJsonValue> ToList(IEnumerable<object?>? values)
{
return values?.Select(JsonValue.Create).ToList() ?? new List<IJsonValue>();
}

10
backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs

@ -8,6 +8,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NodaTime;
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
@ -32,9 +33,14 @@ namespace Squidex.Infrastructure.Json.Objects
return new JsonArray();
}
public static JsonArray Array(params object?[] values)
public static JsonArray Array<T>(IEnumerable<T> values)
{
return new JsonArray(values);
return new JsonArray(values?.OfType<object>());
}
public static JsonArray Array<T>(params T?[] values)
{
return new JsonArray(values?.OfType<object>());
}
public static JsonObject Object()

2
backend/src/Squidex.Infrastructure/Language.cs

@ -7,11 +7,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
namespace Squidex.Infrastructure
{
[TypeConverter(typeof(LanguageTypeConverter))]
public partial record Language
{
private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase);

36
backend/src/Squidex.Infrastructure/LanguageTypeConverter.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel;
using System.Globalization;
namespace Squidex.Infrastructure
{
public sealed class LanguageTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return Language.GetLanguage((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return ((Language)value).Iso2Code;
}
}
}

83
backend/src/Squidex.Infrastructure/NamedIdTypeConverter.cs

@ -0,0 +1,83 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel;
using System.Globalization;
namespace Squidex.Infrastructure
{
internal sealed class NamedIdTypeConverter : TypeConverter
{
private static readonly Parser<Guid> ParserGuid = Guid.TryParse;
private static readonly Parser<DomainId> ParserDomainId = ParseDomainId;
private static readonly Parser<string> ParserString = ParseString;
private static readonly Parser<long> ParserLong = long.TryParse;
private readonly Func<string, object>? converter;
public NamedIdTypeConverter(Type type)
{
var genericType = type?.GetGenericArguments()?[0];
if (genericType == typeof(Guid))
{
converter = v => NamedId<Guid>.Parse(v, ParserGuid);
}
else if (genericType == typeof(DomainId))
{
converter = v => NamedId<DomainId>.Parse(v, ParserDomainId);
}
else if (genericType == typeof(string))
{
converter = v => NamedId<string>.Parse(v, ParserString);
}
else if (genericType == typeof(long))
{
converter = v => NamedId<long>.Parse(v, ParserLong);
}
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (converter == null)
{
throw new NotSupportedException();
}
return converter((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return value.ToString()!;
}
private static bool ParseDomainId(ReadOnlySpan<char> value, out DomainId result)
{
result = DomainId.Create(new string(value));
return true;
}
private static bool ParseString(ReadOnlySpan<char> value, out string result)
{
result = new string(value);
return true;
}
}
}

165
backend/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs

@ -1,165 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation;
namespace Squidex.Infrastructure.Queries.Json
{
public sealed class FilterConverter : JsonClassConverter<FilterNode<IJsonValue>>
{
public override IEnumerable<Type> SupportedTypes
{
get
{
yield return typeof(CompareFilter<IJsonValue>);
yield return typeof(FilterNode<IJsonValue>);
yield return typeof(LogicalFilter<IJsonValue>);
yield return typeof(NegateFilter<IJsonValue>);
}
}
public override bool CanConvert(Type objectType)
{
return SupportedTypes.Contains(objectType);
}
protected override FilterNode<IJsonValue> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
{
throw new JsonException($"Expected StartObject, but got {reader.TokenType}.");
}
FilterNode<IJsonValue>? result = null;
PropertyPath? comparePath = null;
var compareOperator = (CompareOperator)99;
IJsonValue? compareValue = null;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
var propertyName = reader.Value!.ToString()!;
if (!reader.Read())
{
throw new JsonSerializationException("Unexpected end when reading filter.");
}
if (result != null)
{
throw new JsonSerializationException($"Unexpected property {propertyName}");
}
switch (propertyName.ToLowerInvariant())
{
case "not":
var filter = serializer.Deserialize<FilterNode<IJsonValue>>(reader)!;
result = new NegateFilter<IJsonValue>(filter);
break;
case "and":
var andFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader)!;
result = new LogicalFilter<IJsonValue>(LogicalFilterType.And, andFilters);
break;
case "or":
var orFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader)!;
result = new LogicalFilter<IJsonValue>(LogicalFilterType.Or, orFilters);
break;
case "path":
comparePath = serializer.Deserialize<PropertyPath>(reader);
break;
case "op":
compareOperator = ReadOperator(reader, serializer);
break;
case "value":
compareValue = serializer.Deserialize<IJsonValue>(reader);
break;
}
break;
case JsonToken.Comment:
break;
case JsonToken.EndObject:
if (result != null)
{
return result;
}
if (comparePath == null)
{
throw new JsonSerializationException("Path not defined.");
}
if (compareValue == null && compareOperator != CompareOperator.Empty)
{
throw new JsonSerializationException("Value not defined.");
}
if (!compareOperator.IsEnumValue())
{
throw new JsonSerializationException("Operator not defined.");
}
return new CompareFilter<IJsonValue>(comparePath, compareOperator, compareValue ?? JsonValue.Null);
}
}
throw new JsonSerializationException("Unexpected end when reading filter.");
}
private static CompareOperator ReadOperator(JsonReader reader, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
switch (value.ToLowerInvariant())
{
case "eq":
return CompareOperator.Equals;
case "ne":
return CompareOperator.NotEquals;
case "lt":
return CompareOperator.LessThan;
case "le":
return CompareOperator.LessThanOrEqual;
case "gt":
return CompareOperator.GreaterThan;
case "ge":
return CompareOperator.GreaterThanOrEqual;
case "empty":
return CompareOperator.Empty;
case "contains":
return CompareOperator.Contains;
case "endswith":
return CompareOperator.EndsWith;
case "startswith":
return CompareOperator.StartsWith;
case "in":
return CompareOperator.In;
}
throw new JsonSerializationException($"Unexpected compare operator, got {value}.");
}
protected override void WriteValue(JsonWriter writer, FilterNode<IJsonValue> value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
}

91
backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs

@ -0,0 +1,91 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.Queries
{
public sealed class JsonFilterSurrogate : ISurrogate<FilterNode<IJsonValue>>
{
public FilterNode<IJsonValue>[]? And { get; set; }
public FilterNode<IJsonValue>[]? Or { get; set; }
public FilterNode<IJsonValue>? Not { get; set; }
public string? Op { get; set; }
public string? Path { get; set; }
public IJsonValue? Value { get; set; }
public void FromSource(FilterNode<IJsonValue> source)
{
throw new NotSupportedException();
}
public FilterNode<IJsonValue> ToSource()
{
if (Not != null)
{
return new NegateFilter<IJsonValue>(Not);
}
if (And != null)
{
return new LogicalFilter<IJsonValue>(LogicalFilterType.And, And);
}
if (Or != null)
{
return new LogicalFilter<IJsonValue>(LogicalFilterType.Or, Or);
}
if (!string.IsNullOrWhiteSpace(Path) && !string.IsNullOrWhiteSpace(Op))
{
var @operator = ReadOperator(Op);
return new CompareFilter<IJsonValue>(Path, @operator, Value ?? JsonValue.Null);
}
throw new JsonException("Invalid query.");
}
private static CompareOperator ReadOperator(string op)
{
switch (op.ToLowerInvariant())
{
case "eq":
return CompareOperator.Equals;
case "ne":
return CompareOperator.NotEquals;
case "lt":
return CompareOperator.LessThan;
case "le":
return CompareOperator.LessThanOrEqual;
case "gt":
return CompareOperator.GreaterThan;
case "ge":
return CompareOperator.GreaterThanOrEqual;
case "empty":
return CompareOperator.Empty;
case "contains":
return CompareOperator.Contains;
case "endswith":
return CompareOperator.EndsWith;
case "startswith":
return CompareOperator.StartsWith;
case "in":
return CompareOperator.In;
}
throw new JsonException($"Unexpected compare operator, got {op}.");
}
}
}

29
backend/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs

@ -1,29 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Infrastructure.Queries.Json
{
public sealed class PropertyPathConverter : JsonClassConverter<PropertyPath>
{
protected override PropertyPath ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader)!;
return value;
}
protected override void WriteValue(JsonWriter writer, PropertyPath value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToList());
}
}
}

1
backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs

@ -7,7 +7,6 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NJsonSchema;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;

2
backend/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs

@ -176,7 +176,7 @@ namespace Squidex.Infrastructure.Queries.OData
return Instant.FromUtc(date.Year, date.Month, date.Day, 0, 0);
}
var parseResult = InstantPattern.General.Parse(value.ToString()!);
var parseResult = InstantPattern.ExtendedIso.Parse(value.ToString()!);
if (!parseResult.Success)
{

57
backend/src/Squidex.Infrastructure/RefToken.cs

@ -6,39 +6,52 @@
// ==========================================================================
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Squidex.Infrastructure
{
[TypeConverter(typeof(RefTokenTypeConverter))]
public sealed record RefToken
{
public string Type { get; }
private static readonly char[] TrimChars = { ' ', ':' };
public RefTokenType Type { get; }
public string Identifier { get; }
public bool IsClient
{
get { return string.Equals(Type, RefTokenType.Client, StringComparison.OrdinalIgnoreCase); }
get { return Type == RefTokenType.Client; }
}
public bool IsSubject
public bool IsUser
{
get { return string.Equals(Type, RefTokenType.Subject, StringComparison.OrdinalIgnoreCase); }
get { return Type == RefTokenType.Subject; }
}
public RefToken(string type, string identifier)
public RefToken(RefTokenType type, string identifier)
{
Guard.NotNullOrEmpty(type, nameof(type));
Guard.NotNullOrEmpty(identifier, nameof(identifier));
Type = type.ToLowerInvariant();
Type = type;
Identifier = identifier;
}
public static RefToken Client(string identifier)
{
return new RefToken(RefTokenType.Client, identifier);
}
public static RefToken User(string identifier)
{
return new RefToken(RefTokenType.Subject, identifier);
}
public override string ToString()
{
return $"{Type}:{Identifier}";
return $"{Type.ToString().ToLowerInvariant()}:{Identifier}";
}
public override int GetHashCode()
@ -46,30 +59,42 @@ namespace Squidex.Infrastructure
return (Type.GetHashCode() * 397) ^ Identifier.GetHashCode();
}
public static bool TryParse(string value, [MaybeNullWhen(false)] out RefToken result)
public static bool TryParse(string? value, [MaybeNullWhen(false)] out RefToken result)
{
if (value != null)
value = value?.Trim(TrimChars);
if (string.IsNullOrWhiteSpace(value))
{
result = null!;
return false;
}
value = value.Trim();
var idx = value.IndexOf(':');
if (idx > 0 && idx < value.Length - 1)
{
result = new RefToken(value.Substring(0, idx), value[(idx + 1)..]);
if (!Enum.TryParse<RefTokenType>(value.Substring(0, idx), true, out var type))
{
type = RefTokenType.Subject;
}
return true;
result = new RefToken(type, value[(idx + 1)..]);
}
else
{
result = new RefToken(RefTokenType.Subject, value);
}
result = null!;
return false;
return true;
}
public static RefToken Parse(string value)
{
if (!TryParse(value, out var result))
{
throw new ArgumentException("Ref token must have more than 2 parts divided by colon.", nameof(value));
throw new ArgumentException("Ref token cannot be null or empty.", nameof(value));
}
return result;

7
backend/src/Squidex.Infrastructure/RefTokenType.cs

@ -7,10 +7,9 @@
namespace Squidex.Infrastructure
{
public static class RefTokenType
public enum RefTokenType
{
public const string Subject = "subject";
public const string Client = "client";
Subject,
Client
}
}

36
backend/src/Squidex.Infrastructure/RefTokenTypeConverter.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel;
using System.Globalization;
namespace Squidex.Infrastructure
{
public sealed class RefTokenTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return RefToken.Parse((string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
return value.ToString()!;
}
}
}

4
backend/src/Squidex.Infrastructure/Security/Extensions.cs

@ -19,14 +19,14 @@ namespace Squidex.Infrastructure.Security
if (!string.IsNullOrWhiteSpace(subjectId))
{
return new RefToken(RefTokenType.Subject, subjectId);
return RefToken.User(subjectId);
}
var clientId = principal.OpenIdClientId();
if (!string.IsNullOrWhiteSpace(clientId))
{
return new RefToken(RefTokenType.Client, clientId);
return RefToken.Client(clientId);
}
return null;

2
backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs

@ -191,7 +191,7 @@ namespace Squidex.Infrastructure.States
private EventData[] GetEventData(Envelope<IEvent>[] events, Guid commitId)
{
return events.Map(x => eventDataFormatter.ToEventData(x, commitId, true));
return events.Select(x => eventDataFormatter.ToEventData(x, commitId, true)).ToArray();
}
private string GetStreamName()

2
backend/src/Squidex.Web/ContextExtensions.cs

@ -18,7 +18,7 @@ namespace Squidex.Web
if (context == null)
{
context = RequestContext.Anonymous(null);
context = RequestContext.Anonymous(null!);
httpContext.Features.Set(context);
}

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

Loading…
Cancel
Save