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. 33
      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/CachingGraphQLService.cs
  52. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs
  53. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/EntityResolvers.cs
  54. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/InstantGraphType.cs
  55. 6
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryService.cs
  56. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  57. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  58. 2
      backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs
  59. 5
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs
  60. 7
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs
  61. 32
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs
  62. 2
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs
  63. 3
      backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs
  64. 13
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  65. 53
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/JTokenSerializer.cs
  66. 2
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  67. 40
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs
  68. 30
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/TypeConverterStringSerializer.cs
  69. 12
      backend/src/Squidex.Infrastructure/CollectionExtensions.cs
  70. 2
      backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  71. 13
      backend/src/Squidex.Infrastructure/ISurrogate.cs
  72. 78
      backend/src/Squidex.Infrastructure/Json/ClaimsPrinicpalSurrogate.cs
  73. 2
      backend/src/Squidex.Infrastructure/Json/ISupportedTypes.cs
  74. 35
      backend/src/Squidex.Infrastructure/Json/JsonException.cs
  75. 62
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs
  76. 62
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/DomainIdConverter.cs
  77. 69
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs
  78. 27
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs
  79. 41
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedDomainIdConverter.cs
  80. 34
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs
  81. 34
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs
  82. 41
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs
  83. 48
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs
  84. 32
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs
  85. 31
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/SurrogateConverter.cs
  86. 4
      backend/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs
  87. 10
      backend/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs
  88. 2
      backend/src/Squidex.Infrastructure/Language.cs
  89. 36
      backend/src/Squidex.Infrastructure/LanguageTypeConverter.cs
  90. 83
      backend/src/Squidex.Infrastructure/NamedIdTypeConverter.cs
  91. 165
      backend/src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs
  92. 91
      backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs
  93. 29
      backend/src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs
  94. 1
      backend/src/Squidex.Infrastructure/Queries/Json/QueryParser.cs
  95. 2
      backend/src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs
  96. 63
      backend/src/Squidex.Infrastructure/RefToken.cs
  97. 7
      backend/src/Squidex.Infrastructure/RefTokenType.cs
  98. 36
      backend/src/Squidex.Infrastructure/RefTokenTypeConverter.cs
  99. 4
      backend/src/Squidex.Infrastructure/Security/Extensions.cs
  100. 2
      backend/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.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}")); json = new JObject(new JProperty("error", $"Invalid JSON: {ex.Message}"));
} }
ruleJob.Content = json; json["objectID"] = contentId;
ruleJob.Content["objectID"] = contentId;
ruleJob.Content = json.ToString();
} }
return (ruleDescription, ruleJob); return (ruleDescription, ruleJob);
@ -135,6 +136,6 @@ namespace Squidex.Extensions.Actions.Algolia
public string IndexName { get; set; } 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)) if (!string.IsNullOrEmpty(action.Client))
{ {
ruleJob.Actor = new RefToken(RefTokenType.Client, action.Client); ruleJob.Actor = RefToken.Client(action.Client);
} }
else else
{ {

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

@ -61,7 +61,7 @@ namespace Squidex.Extensions.Actions.CreateContent
if (!string.IsNullOrEmpty(action.Client)) 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) 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;
using Confluent.SchemaRegistry.Serdes; using Confluent.SchemaRegistry.Serdes;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Log; 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)) if (!string.IsNullOrEmpty(action.Client))
{ {
actor = new RefToken(RefTokenType.Client, action.Client); actor = RefToken.Client(action.Client);
} }
var user = await userResolver.FindByIdOrEmailAsync(action.User); 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 System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
@ -53,10 +52,10 @@ namespace Migrations.Migrations
{ {
foreach (BsonDocument @event in commit["Events"].AsBsonArray) 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.Remove("EventId");
@event["Metadata"] = meta.ToBson(); @event["Metadata"] = meta;
} }
await WriteAsync(new ReplaceOneModel<BsonDocument>(filter.Eq("_id", commit["_id"].AsString), commit), false); 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 System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
@ -60,11 +59,11 @@ namespace Migrations.Migrations
foreach (BsonDocument @event in commit["Events"].AsBsonArray) 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)) 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); 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 System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
public class JsonLanguageConfig public sealed class LanguageConfigSurrogate : ISurrogate<LanguageConfig>
{ {
[JsonProperty]
public Language[]? Fallback { get; set; } public Language[]? Fallback { get; set; }
[JsonProperty]
public bool IsOptional { get; set; } public bool IsOptional { get; set; }
public JsonLanguageConfig() public void FromSource(LanguageConfig source)
{ {
} IsOptional = source.IsOptional;
public JsonLanguageConfig(LanguageConfig config)
{
SimpleMapper.Map(config, this);
Fallback = config.Fallbacks.ToArray(); Fallback = source.Fallbacks.ToArray();
} }
public LanguageConfig ToConfig() public LanguageConfig ToSource()
{ {
if (!IsOptional && (Fallback == null || Fallback.Length == 0)) 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
public sealed class JsonLanguagesConfig public sealed class LanguagesConfigSurrogate : ISurrogate<LanguagesConfig>
{ {
[JsonProperty] public Dictionary<string, LanguageConfigSurrogate> Languages { get; set; }
public Dictionary<string, JsonLanguageConfig> Languages { get; set; }
[JsonProperty]
public string Master { get; set; } public string Master { get; set; }
public JsonLanguagesConfig() public void FromSource(LanguagesConfig source)
{ {
} Languages = source.Languages.ToDictionary(x => x.Key, source =>
{
var surrogate = new LanguageConfigSurrogate();
public JsonLanguagesConfig(LanguagesConfig value) surrogate.FromSource(source.Value);
{
Languages = value.Languages.ToDictionary(x => x.Key, x => new JsonLanguageConfig(x.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(); 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)); this[InvariantPartitioning.Key] = 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;
return this; return this;
} }
public ContentFieldData AddJsonValue(string key, IJsonValue value) public ContentFieldData AddLocalized(string key, object? value)
{ {
Guard.NotNullOrEmpty(key, nameof(key)); Guard.NotNullOrEmpty(key, nameof(key));
if (Language.IsValidLanguage(key)) this[key] = JsonValue.Create(value);
{
this[key] = value;
}
else
{
this[key] = value;
}
return this; 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();
}
}
}

33
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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Contents.Json namespace Squidex.Domain.Apps.Core.Contents.Json
{ {
public sealed class JsonWorkflowStep public sealed class WorkflowStepSurrogate : ISurrogate<WorkflowStep>
{ {
[JsonProperty] public Dictionary<Status, WorkflowTransitionSurrogate> Transitions { get; set; }
public Dictionary<Status, JsonWorkflowTransition> Transitions { get; set; }
[JsonProperty]
public string? Color { get; set; }
[JsonProperty("noUpdate")] [JsonProperty("noUpdate")]
public bool NoUpdateFlag { get; set; } public bool NoUpdateFlag { get; set; }
@ -26,21 +23,23 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
[JsonProperty("noUpdateRules")] [JsonProperty("noUpdateRules")]
public NoUpdate? NoUpdate { get; set; } public NoUpdate? NoUpdate { get; set; }
public JsonWorkflowStep() public string? Color { get; set; }
{
}
public JsonWorkflowStep(WorkflowStep step) public void FromSource(WorkflowStep source)
{ {
SimpleMapper.Map(step, this); SimpleMapper.Map(source, this);
Transitions = Transitions = source.Transitions.ToDictionary(x => x.Key, source =>
step.Transitions.ToDictionary( {
x => x.Key, var surrogate = new WorkflowTransitionSurrogate();
x => new JsonWorkflowTransition(x.Value));
surrogate.FromSource(source.Value);
return surrogate;
});
} }
public WorkflowStep ToStep() public WorkflowStep ToSource()
{ {
var noUpdate = NoUpdate; var noUpdate = NoUpdate;
@ -52,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
var transitions = var transitions =
Transitions?.ToDictionary( Transitions?.ToDictionary(
x => x.Key, x => x.Key,
x => x.Value.ToTransition()); x => x.Value.ToSource());
return new WorkflowStep(transitions, Color, noUpdate); 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 System.Linq;
using Newtonsoft.Json; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Contents.Json namespace Squidex.Domain.Apps.Core.Contents.Json
{ {
public class JsonWorkflowTransition public sealed class WorkflowTransitionSurrogate : ISurrogate<WorkflowTransition>
{ {
[JsonProperty]
public string? Expression { get; set; } public string? Expression { get; set; }
[JsonProperty]
public string? Role { get; set; } public string? Role { get; set; }
[JsonProperty]
public string[]? Roles { get; set; } public string[]? Roles { get; set; }
public JsonWorkflowTransition() public void FromSource(WorkflowTransition source)
{
}
public JsonWorkflowTransition(WorkflowTransition transition)
{ {
Roles = transition.Roles?.ToArray(); Roles = source.Roles?.ToArray();
Expression = transition.Expression; Expression = source.Expression;
} }
public WorkflowTransition ToTransition() public WorkflowTransition ToSource()
{ {
var roles = Roles; 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. // 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 namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class StatusInfo public sealed record StatusInfo(Status Status, string Color)
{ {
public Status Status { get; }
public string Color { get; }
public 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) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string s) return new Status((string)value);
{
return new Status(s);
}
return default(Status);
} }
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json; using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Rules.Json namespace Squidex.Domain.Apps.Core.Rules.Json
{ {
public sealed class JsonRule public sealed class RuleSorrgate : ISurrogate<Rule>
{ {
[JsonProperty]
public RuleTrigger Trigger { get; set; } public RuleTrigger Trigger { get; set; }
[JsonProperty]
public RuleAction Action { get; set; } public RuleAction Action { get; set; }
[JsonProperty]
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
[JsonProperty]
public string Name { get; set; } public string Name { get; set; }
public JsonRule() public void FromSource(Rule source)
{ {
SimpleMapper.Map(source, this);
} }
public JsonRule(Rule rule) public Rule ToSource()
{
SimpleMapper.Map(rule, this);
}
public Rule ToRule()
{ {
var trigger = Trigger; 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 System;
using Newtonsoft.Json; using System.Linq;
using Squidex.Infrastructure;
using P = Squidex.Domain.Apps.Core.Partitioning;
namespace Squidex.Domain.Apps.Core.Schemas.Json namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonFieldModel : IFieldSettings public sealed class FieldSurrogate : IFieldSettings
{ {
[JsonProperty]
public long Id { get; set; } public long Id { get; set; }
[JsonProperty]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty]
public string Partitioning { get; set; } public string Partitioning { get; set; }
[JsonProperty]
public bool IsHidden { get; set; } public bool IsHidden { get; set; }
[JsonProperty]
public bool IsLocked { get; set; } public bool IsLocked { get; set; }
[JsonProperty]
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }
[JsonProperty]
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }
[JsonProperty] public FieldSurrogate[]? Children { get; set; }
public JsonNestedFieldModel[]? Children { get; set; }
public RootField ToField() public RootField ToField()
{ {
var partitioning = P.FromString(Partitioning); var partitioning = Core.Partitioning.FromString(Partitioning);
if (Properties is ArrayFieldProperties arrayProperties) 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); 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); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Schemas.Json namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonSchemaModel public sealed class SchemaSurrogate : ISurrogate<Schema>
{ {
[JsonProperty]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty]
public string Category { get; set; } public string Category { get; set; }
[JsonProperty]
public bool IsSingleton { get; set; } public bool IsSingleton { get; set; }
[JsonProperty]
public bool IsPublished { get; set; } public bool IsPublished { get; set; }
[JsonProperty]
public SchemaProperties Properties { get; set; } public SchemaProperties Properties { get; set; }
[JsonProperty]
public SchemaScripts? Scripts { get; set; } public SchemaScripts? Scripts { get; set; }
[JsonProperty]
public FieldNames? FieldsInLists { get; set; } public FieldNames? FieldsInLists { get; set; }
[JsonProperty]
public FieldNames? FieldsInReferences { get; set; } public FieldNames? FieldsInReferences { get; set; }
[JsonProperty]
public FieldRules? FieldRules { get; set; } public FieldRules? FieldRules { get; set; }
[JsonProperty] public FieldSurrogate[] Fields { get; set; }
public JsonFieldModel[] Fields { get; set; }
[JsonProperty]
public Dictionary<string, string>? PreviewUrls { get; set; } public Dictionary<string, string>? PreviewUrls { get; set; }
public JsonSchemaModel() public void FromSource(Schema source)
{ {
} SimpleMapper.Map(source, this);
public JsonSchemaModel(Schema schema)
{
SimpleMapper.Map(schema, this);
Fields = Fields =
schema.Fields.Select(x => source.Fields.Select(x =>
new JsonFieldModel new FieldSurrogate
{ {
Id = x.Id, Id = x.Id,
Name = x.Name, Name = x.Name,
@ -71,15 +55,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
Properties = x.RawProperties Properties = x.RawProperties
}).ToArray(); }).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) if (field is ArrayField arrayField)
{ {
return arrayField.Fields.Select(x => return arrayField.Fields.Select(x =>
new JsonNestedFieldModel new FieldSurrogate
{ {
Id = x.Id, Id = x.Id,
Name = x.Name, Name = x.Name,
@ -93,9 +77,9 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
return null; 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); 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)) 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 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) public static void AddReferencedIds(this ContentData source, Schema schema, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(schema, nameof(schema)); 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; break;
case "timestamp": case "timestamp":
{ {
var instant = InstantPattern.General.Parse(text); var instant = InstantPattern.ExtendedIso.Parse(text);
if (instant.Success) if (instant.Success)
{ {
@ -299,7 +299,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
case "timestamp_sec": case "timestamp_sec":
{ {
var instant = InstantPattern.General.Parse(text); var instant = InstantPattern.ExtendedIso.Parse(text);
if (instant.Success) 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 value = stringValue.ToStringValue();
var instant = InstantPattern.General.Parse(value); var instant = InstantPattern.ExtendedIso.Parse(value);
if (instant.Success) 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(type, x => new StringValue(x.ToString()));
} }
FluidValue.SetTypeMapping<RefTokenType>(x => new StringValue(x.ToString().ToLowerInvariant()));
globalTypes.Register<NamedId<DomainId>>(); globalTypes.Register<NamedId<DomainId>>();
globalTypes.Register<NamedId<Guid>>(); globalTypes.Register<NamedId<Guid>>();
globalTypes.Register<NamedId<string>>(); 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) 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) 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) 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; 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.Domain.Apps.Entities.Schemas;
using Squidex.Hosting; using Squidex.Hosting;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents 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 collectionAll;
private readonly MongoContentCollection collectionPublished; private readonly MongoContentCollection collectionPublished;
private readonly IAppProvider appProvider;
static MongoContentRepository() static MongoContentRepository()
{ {
StatusSerializer.Register(); TypeConverterStringSerializer<Status>.Register();
} }
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex) public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex)
@ -43,6 +45,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
collectionPublished = collectionPublished =
new MongoContentCollection( new MongoContentCollection(
"States_Contents_Published3", database, appProvider, useWildcardIndex); "States_Contents_Published3", database, appProvider, useWildcardIndex);
this.appProvider = appProvider;
} }
public async Task InitializeAsync(CancellationToken ct = default) 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; 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) 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.ScheduledAt = value.ScheduleJob?.DueTime;
content.ScheduleJob = value.ScheduleJob; content.ScheduleJob = value.ScheduleJob;
content.NewStatus = value.NewStatus; 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) 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.ScheduledAt = null;
content.ScheduleJob = null; content.ScheduleJob = null;
content.NewStatus = null; content.NewStatus = null;
@ -115,15 +114,23 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await collectionPublished.UpsertVersionedAsync(content.DocumentId, oldVersion, content); 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()); var content = SimpleMapper.Map(value, new MongoContentEntity());
content.Data = data;
content.DocumentId = value.UniqueId; content.DocumentId = value.UniqueId;
content.IndexedAppId = value.AppId.Id; content.IndexedAppId = value.AppId.Id;
content.IndexedSchemaId = value.SchemaId.Id; content.IndexedSchemaId = value.SchemaId.Id;
content.Version = newVersion; 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; 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() CreateInitialLanguage()
}; };
if (command.Actor.IsSubject) if (command.Actor.IsUser)
{ {
events.Add(CreateInitialOwner(command.Actor)); 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 (@event.Payload is AppContributorAssigned appContributorAssigned)
{ {
if (!appContributorAssigned.Actor.IsSubject || !appContributorAssigned.IsAdded) if (!appContributorAssigned.Actor.IsUser || !appContributorAssigned.IsAdded)
{ {
return; 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() new ContentData()
.AddField("title", .AddField("title",
new ContentFieldData() new ContentFieldData()
.AddValue("My first post with Squidex")) .AddInvariant("My first post with Squidex"))
.AddField("text", .AddField("text",
new ContentFieldData() new ContentFieldData()
.AddValue("Just created a blog with Squidex. I love it!")), .AddInvariant("Just created a blog with Squidex. I love it!")),
Publish = true Publish = true
}); });
} }
@ -79,10 +79,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
new ContentData() new ContentData()
.AddField("title", .AddField("title",
new ContentFieldData() new ContentFieldData()
.AddValue("About Me")) .AddInvariant("About Me"))
.AddField("text", .AddField("text",
new ContentFieldData() new ContentFieldData()
.AddValue("I love Squidex and SciFi!")), .AddInvariant("I love Squidex and SciFi!")),
Publish = true 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() new ContentData()
.AddField("firstName", .AddField("firstName",
new ContentFieldData() new ContentFieldData()
.AddValue("John")) .AddInvariant("John"))
.AddField("lastName", .AddField("lastName",
new ContentFieldData() new ContentFieldData()
.AddValue("Doe")) .AddInvariant("Doe"))
.AddField("profession", .AddField("profession",
new ContentFieldData() new ContentFieldData()
.AddValue("Software Developer")), .AddInvariant("Software Developer")),
SchemaId = postsId 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; var actor = CurrentJob.Actor;
if (actor?.IsSubject == true) if (actor?.IsUser == true)
{ {
try 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)); Guard.NotNull(token, nameof(token));
if (!token.IsSubject) if (!token.IsUser)
{ {
return; return;
} }
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (!userMap.ContainsKey(userId)) 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) 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)); Guard.NotNullOrEmpty(userId, nameof(userId));
result = initiator;
if (userMap.TryGetValue(userId, out var mapped)) if (userMap.TryGetValue(userId, out var mapped))
{ {
result = mapped; result = mapped;
return true; return true;
} }
result = initiator;
return false; return false;
} }
@ -101,6 +102,8 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
Guard.NotNull(token, nameof(token)); Guard.NotNull(token, nameof(token));
result = initiator;
if (token.IsClient) if (token.IsClient)
{ {
result = token; result = token;
@ -113,7 +116,6 @@ namespace Squidex.Domain.Apps.Entities.Backup
return true; return true;
} }
result = initiator;
return false; return false;
} }
} }

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

@ -108,4 +108,4 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return $"GraphQLModel_{appId}_{etag}"; return $"GraphQLModel_{appId}_{etag}";
} }
} }
} }

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<JsonNumber, double>(x => x.Value);
ValueConverter.Register<JsonString, string>(x => x.Value); ValueConverter.Register<JsonString, string>(x => x.Value);
ValueConverter.Register<JsonString, DateTimeOffset>(x => DateTimeOffset.Parse(x.Value, CultureInfo.InvariantCulture)); ValueConverter.Register<JsonString, DateTimeOffset>(x => DateTimeOffset.Parse(x.Value, CultureInfo.InvariantCulture));
ValueConverter.Register<string, DomainId>(DomainId.Create); ValueConverter.Register<string, DomainId>(DomainId.Create);
ValueConverter.Register<string, Status>(x => new Status(x)); 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 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 Created = Resolve<IEntity>(x => x.Created);
public static readonly IFieldResolver CreatedBy = Resolve<IEntityWithCreatedBy>(x => x.CreatedBy.ToString()); public static readonly IFieldResolver CreatedBy = Resolve<IEntityWithCreatedBy>(x => x.CreatedBy);
public static readonly IFieldResolver LastModified = Resolve<IEntity>(x => x.LastModified.ToString()); public static readonly IFieldResolver LastModified = Resolve<IEntity>(x => x.LastModified);
public static readonly IFieldResolver LastModifiedBy = Resolve<IEntityWithLastModifiedBy>(x => x.LastModifiedBy.ToString()); public static readonly IFieldResolver LastModifiedBy = Resolve<IEntityWithLastModifiedBy>(x => x.LastModifiedBy);
public static readonly IFieldResolver Version = Resolve<IEntityWithVersion>(x => x.Version); public static readonly IFieldResolver Version = Resolve<IEntityWithVersion>(x => x.Version);
private static IFieldResolver Resolve<TSource>(Func<TSource, object> resolver) 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 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) public override object Serialize(object value)
{ {
return ParseValue(value); return value;
} }
public override object ParseValue(object 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) 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, IAppProvider appProvider,
IContentEnricher contentEnricher, IContentEnricher contentEnricher,
IContentRepository contentRepository, IContentRepository contentRepository,
IContentLoader assetLoader, IContentLoader contentLoader,
ContentQueryParser queryParser) ContentQueryParser queryParser)
{ {
Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(contentEnricher, nameof(contentEnricher)); Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contentRepository, nameof(contentRepository)); Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(assetLoader, nameof(assetLoader)); Guard.NotNull(contentLoader, nameof(contentLoader));
Guard.NotNull(queryParser, nameof(queryParser)); Guard.NotNull(queryParser, nameof(queryParser));
this.appProvider = appProvider; this.appProvider = appProvider;
this.contentEnricher = contentEnricher; this.contentEnricher = contentEnricher;
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
this.contentLoader = assetLoader; this.contentLoader = contentLoader;
this.queryParser = queryParser; this.queryParser = queryParser;
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); 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)); var value = formatted.GetOrAdd(reference, x => Format(x, context, referencedSchema));
fieldReference.AddJsonValue(partition, value); fieldReference.AddLocalized(partition, value);
} }
else if (referencedContents.Count > 1) else if (referencedContents.Count > 1)
{ {
var value = CreateFallback(context, referencedContents); 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) private static void SetUser(AppEvent appEvent, PublishDto publishRequest)
{ {
if (appEvent.Actor.IsSubject) if (appEvent.Actor.IsUser)
{ {
publishRequest.CreatorId = appEvent.Actor.Identifier; 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
internal sealed class CosmosDbEvent internal sealed class CosmosDbEvent
{ {
[JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("payload")]
public string Payload { get; set; } public string Payload { get; set; }
[JsonProperty("header")]
public EnvelopeHeaders Headers { get; set; } public EnvelopeHeaders Headers { get; set; }
public static CosmosDbEvent FromEventData(EventData data) public static CosmosDbEvent FromEventData(EventData data)

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

@ -6,28 +6,21 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
internal sealed class CosmosDbEventCommit internal sealed class CosmosDbEventCommit
{ {
[JsonProperty("id")]
public Guid Id { get; set; } public Guid Id { get; set; }
[JsonProperty("events")]
public CosmosDbEvent[] Events { get; set; } public CosmosDbEvent[] Events { get; set; }
[JsonProperty("eventStreamOffset")]
public long EventStreamOffset { get; set; } public long EventStreamOffset { get; set; }
[JsonProperty("eventsCount")]
public long EventsCount { get; set; } public long EventsCount { get; set; }
[JsonProperty("eventStream")]
public string EventStream { get; set; } public string EventStream { get; set; }
[JsonProperty("timestamp")]
public long Timestamp { get; set; } 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 System.Threading.Tasks;
using Microsoft.Azure.Documents; using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client; using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;
using Squidex.Hosting; using Squidex.Hosting;
using Squidex.Infrastructure.Json;
using Index = Microsoft.Azure.Documents.Index; using Index = Microsoft.Azure.Documents.Index;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
@ -22,47 +22,35 @@ namespace Squidex.Infrastructure.EventSourcing
private readonly DocumentClient documentClient; private readonly DocumentClient documentClient;
private readonly Uri collectionUri; private readonly Uri collectionUri;
private readonly Uri databaseUri; private readonly Uri databaseUri;
private readonly string masterKey;
private readonly string databaseId;
private readonly JsonSerializerSettings serializerSettings;
public JsonSerializerSettings SerializerSettings public IJsonSerializer JsonSerializer { get; }
{
get { return serializerSettings; }
}
public string DatabaseId public string DatabaseId { get; }
{
get { return databaseId; }
}
public string MasterKey public string MasterKey { get; }
{
get { return masterKey; }
}
public Uri ServiceUri public Uri ServiceUri
{ {
get { return documentClient.ServiceEndpoint; } 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(documentClient, nameof(documentClient));
Guard.NotNull(serializerSettings, nameof(serializerSettings)); Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNullOrEmpty(masterKey, nameof(masterKey)); Guard.NotNullOrEmpty(masterKey, nameof(masterKey));
Guard.NotNullOrEmpty(database, nameof(database)); Guard.NotNullOrEmpty(database, nameof(database));
this.documentClient = documentClient; this.documentClient = documentClient;
databaseUri = UriFactory.CreateDatabaseUri(database); databaseUri = UriFactory.CreateDatabaseUri(database);
databaseId = database; DatabaseId = database;
collectionUri = UriFactory.CreateDocumentCollectionUri(database, Constants.Collection); collectionUri = UriFactory.CreateDocumentCollectionUri(database, Constants.Collection);
this.masterKey = masterKey; MasterKey = masterKey;
this.serializerSettings = serializerSettings; JsonSerializer = jsonSerializer;
} }
protected override void DisposeObject(bool disposing) protected override void DisposeObject(bool disposing)
@ -75,7 +63,7 @@ namespace Squidex.Infrastructure.EventSourcing
public async Task InitializeAsync(CancellationToken ct = default) 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, await documentClient.CreateDocumentCollectionIfNotExistsAsync(databaseUri,
new DocumentCollection 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 => 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); 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 System.Threading.Tasks;
using Microsoft.Azure.Documents; using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.ChangeFeedProcessor.FeedProcessing; using Microsoft.Azure.Documents.ChangeFeedProcessor.FeedProcessing;
using Newtonsoft.Json;
using Builder = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorBuilder; using Builder = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorBuilder;
using Collection = Microsoft.Azure.Documents.ChangeFeedProcessor.DocumentCollectionInfo; using Collection = Microsoft.Azure.Documents.ChangeFeedProcessor.DocumentCollectionInfo;
using Options = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorOptions; using Options = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorOptions;
@ -117,7 +116,7 @@ namespace Squidex.Infrastructure.EventSourcing
if (regex == null || regex.IsMatch(streamName)) 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; 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;
using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.Conventions;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Squidex.Infrastructure.MongoDb namespace Squidex.Infrastructure.MongoDb
{ {
@ -35,18 +34,6 @@ namespace Squidex.Infrastructure.MongoDb
memberMap.SetSerializer((IBsonSerializer)bsonSerializer!); 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); 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() static MongoRepositoryBase()
{ {
RefTokenSerializer.Register(); TypeConverterStringSerializer<RefToken>.Register();
InstantSerializer.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());
}
}
}

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

@ -5,27 +5,22 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.ComponentModel;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers; 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() public static void Register()
{ {
try try
{ {
try BsonSerializer.RegisterSerializer(new TypeConverterStringSerializer<T>());
{
BsonSerializer.RegisterSerializer(new StatusSerializer());
}
catch (BsonSerializationException)
{
return;
}
} }
catch (BsonSerializationException) catch (BsonSerializationException)
{ {
@ -33,16 +28,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) public TypeConverterStringSerializer()
{
typeConverter = TypeDescriptor.GetConverter(typeof(T));
}
public override T Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{ {
var value = context.Reader.ReadString(); 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)); 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) public static int SequentialHashCode<T>(this IEnumerable<T> collection)
{ {
return collection.SequentialHashCode(EqualityComparer<T>.Default); 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 (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; 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. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Converters; namespace Squidex.Infrastructure
namespace Squidex.Infrastructure.Json.Newtonsoft
{ {
public sealed class DateConverter : IsoDateTimeConverter public interface ISurrogate<T>
{ {
public DateConverter() void FromSource(T source);
{
DateTimeFormat = "yyyy-MM-dd"; 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;
using System.Collections.Generic; using System.Collections.Generic;
namespace Squidex.Infrastructure.Json.Newtonsoft namespace Squidex.Infrastructure.Json
{ {
public interface ISupportedTypes 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;
}
}
}

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

@ -8,6 +8,7 @@
using System; using System;
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
using NewtonsoftException = Newtonsoft.Json.JsonException;
namespace Squidex.Infrastructure.Json.Newtonsoft namespace Squidex.Infrastructure.Json.Newtonsoft
{ {
@ -32,38 +33,59 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
public void Serialize<T>(T value, Stream stream, bool leaveOpen = false) public void Serialize<T>(T value, Stream stream, bool leaveOpen = false)
{ {
using (var writer = new StreamWriter(stream, leaveOpen: leaveOpen)) try
{ {
serializer.Serialize(writer, value); using (var writer = new StreamWriter(stream, leaveOpen: leaveOpen))
{
serializer.Serialize(writer, value);
writer.Flush(); writer.Flush();
}
}
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
} }
} }
public T Deserialize<T>(string value, Type? actualType = null) public T Deserialize<T>(string value, Type? actualType = null)
{ {
using (var textReader = new StringReader(value)) try
{ {
actualType ??= typeof(T); using (var textReader = new StringReader(value))
using (var reader = GetReader(textReader))
{ {
return (T)serializer.Deserialize(reader, actualType)!; actualType ??= typeof(T);
using (var reader = GetReader(textReader))
{
return (T)serializer.Deserialize(reader, actualType)!;
}
} }
} }
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
}
} }
public T Deserialize<T>(Stream stream, Type? actualType = null, bool leaveOpen = false) public T Deserialize<T>(Stream stream, Type? actualType = null, bool leaveOpen = false)
{ {
using (var textReader = new StreamReader(stream, leaveOpen: leaveOpen)) try
{ {
actualType ??= typeof(T); using (var textReader = new StreamReader(stream, leaveOpen: leaveOpen))
using (var reader = GetReader(textReader))
{ {
return (T)serializer.Deserialize(reader, actualType)!; actualType ??= typeof(T);
using (var reader = GetReader(textReader))
{
return (T)serializer.Deserialize(reader, actualType)!;
}
} }
} }
catch (NewtonsoftException ex)
{
throw new JsonException(ex.Message, ex);
}
} }
private static JsonTextReader GetReader(TextReader textReader) 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)) : 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>(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using NodaTime; using NodaTime;
#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator #pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator
@ -32,9 +33,14 @@ namespace Squidex.Infrastructure.Json.Objects
return new JsonArray(); 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() public static JsonObject Object()

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

@ -7,11 +7,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
[TypeConverter(typeof(LanguageTypeConverter))]
public partial record Language public partial record Language
{ {
private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase); 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.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
using NJsonSchema; using NJsonSchema;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects; 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); 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) if (!parseResult.Success)
{ {

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

@ -6,39 +6,52 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
[TypeConverter(typeof(RefTokenTypeConverter))]
public sealed record RefToken public sealed record RefToken
{ {
public string Type { get; } private static readonly char[] TrimChars = { ' ', ':' };
public RefTokenType Type { get; }
public string Identifier { get; } public string Identifier { get; }
public bool IsClient 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)); Guard.NotNullOrEmpty(identifier, nameof(identifier));
Type = type.ToLowerInvariant(); Type = type;
Identifier = identifier; 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() public override string ToString()
{ {
return $"{Type}:{Identifier}"; return $"{Type.ToString().ToLowerInvariant()}:{Identifier}";
} }
public override int GetHashCode() public override int GetHashCode()
@ -46,30 +59,42 @@ namespace Squidex.Infrastructure
return (Type.GetHashCode() * 397) ^ Identifier.GetHashCode(); 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))
{ {
var idx = value.IndexOf(':'); result = null!;
return false;
}
if (idx > 0 && idx < value.Length - 1) value = value.Trim();
{
result = new RefToken(value.Substring(0, idx), value[(idx + 1)..]); var idx = value.IndexOf(':');
return true; if (idx > 0 && idx < value.Length - 1)
{
if (!Enum.TryParse<RefTokenType>(value.Substring(0, idx), true, out var type))
{
type = RefTokenType.Subject;
} }
}
result = null!; result = new RefToken(type, value[(idx + 1)..]);
}
else
{
result = new RefToken(RefTokenType.Subject, value);
}
return false; return true;
} }
public static RefToken Parse(string value) public static RefToken Parse(string value)
{ {
if (!TryParse(value, out var result)) 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; return result;

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

@ -7,10 +7,9 @@
namespace Squidex.Infrastructure namespace Squidex.Infrastructure
{ {
public static class RefTokenType public enum RefTokenType
{ {
public const string Subject = "subject"; Subject,
Client
public const string Client = "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)) if (!string.IsNullOrWhiteSpace(subjectId))
{ {
return new RefToken(RefTokenType.Subject, subjectId); return RefToken.User(subjectId);
} }
var clientId = principal.OpenIdClientId(); var clientId = principal.OpenIdClientId();
if (!string.IsNullOrWhiteSpace(clientId)) if (!string.IsNullOrWhiteSpace(clientId))
{ {
return new RefToken(RefTokenType.Client, clientId); return RefToken.Client(clientId);
} }
return null; 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) 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() private string GetStreamName()

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

Loading…
Cancel
Save