Browse Source

Query improvements (#477)

* Simplify the query code.
* Custom deep equals.
* Cleanup.
* Json fix.
* Equal fixed for custom equality comparer.
* Better equals solution.
* Fixes
pull/479/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
8e2f3e9d7e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs
  2. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs
  3. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppImage.cs
  4. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs
  5. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  6. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs
  7. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs
  8. 9
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  9. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  10. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  11. 15
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/JsonWorkflowTransition.cs
  12. 19
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/NoUpdate.cs
  13. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
  14. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs
  15. 15
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowCondition.cs
  16. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowStep.cs
  17. 19
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowTransition.cs
  18. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs
  19. 2
      backend/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml
  20. 1
      backend/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd
  21. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Freezable.cs
  22. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs
  23. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs
  24. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs
  25. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  26. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  27. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs
  28. 7
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  29. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs
  30. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs
  31. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  32. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  33. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  34. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  35. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  36. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaProperties.cs
  37. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs
  38. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  39. 1
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs
  40. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  41. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs
  42. 7
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  43. 15
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  44. 270
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  45. 86
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  46. 4
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  47. 30
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  48. 39
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs
  49. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/AdaptionVisitor.cs
  50. 35
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/CleanupReferences.cs
  51. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs
  52. 39
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs
  53. 49
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContent.cs
  54. 128
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs
  55. 159
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs
  56. 88
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs
  57. 44
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduledContents.cs
  58. 134
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
  59. 51
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  60. 5
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs
  61. 8
      backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs
  62. 4
      backend/src/Squidex.Infrastructure/CollectionExtensions.cs
  63. 4
      backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary{TKey,TValue}.cs
  64. 3
      backend/src/Squidex.Infrastructure/FodyWeavers.xml
  65. 26
      backend/src/Squidex.Infrastructure/FodyWeavers.xsd
  66. 3
      backend/src/Squidex.Infrastructure/Guard.cs
  67. 2
      backend/src/Squidex.Infrastructure/Language.cs
  68. 18
      backend/src/Squidex.Infrastructure/NamedId{T}.cs
  69. 15
      backend/src/Squidex.Infrastructure/RefToken.cs
  70. 40
      backend/src/Squidex.Infrastructure/Reflection/Equality/ArrayComparer.cs
  71. 69
      backend/src/Squidex.Infrastructure/Reflection/Equality/CollectionComparer.cs
  72. 13
      backend/src/Squidex.Infrastructure/Reflection/Equality/DeepEqualityComparer.cs
  73. 36
      backend/src/Squidex.Infrastructure/Reflection/Equality/DefaultComparer.cs
  74. 37
      backend/src/Squidex.Infrastructure/Reflection/Equality/DictionaryComparer{TKey,TValue}.cs
  75. 14
      backend/src/Squidex.Infrastructure/Reflection/Equality/IDeepComparer.cs
  76. 17
      backend/src/Squidex.Infrastructure/Reflection/Equality/NoopComparer.cs
  77. 50
      backend/src/Squidex.Infrastructure/Reflection/Equality/ObjectComparer.cs
  78. 35
      backend/src/Squidex.Infrastructure/Reflection/Equality/SetComparer.cs
  79. 10
      backend/src/Squidex.Infrastructure/Reflection/IgnoreEqualsAttribute.cs
  80. 15
      backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs
  81. 0
      backend/src/Squidex.Infrastructure/Reflection/Internal/ReflectionExtensions.cs
  82. 78
      backend/src/Squidex.Infrastructure/Reflection/PropertiesTypeAccessor.cs
  83. 84
      backend/src/Squidex.Infrastructure/Reflection/SimpleCopier.cs
  84. 121
      backend/src/Squidex.Infrastructure/Reflection/SimpleEquals.cs
  85. 15
      backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs
  86. 4
      backend/src/Squidex.Infrastructure/Security/Permission.cs
  87. 5
      backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  88. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowTransitionDto.cs
  89. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs
  90. 14
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs
  91. 2
      backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  92. 56
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs
  93. 18
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs
  94. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetFolderDomainObjectTests.cs
  95. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs
  96. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsGrainTests.cs
  97. 34
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs
  98. 9
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs
  99. 10
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs
  100. 60
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs

@ -10,6 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class AppClient : Named
{
public string Role { get; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs

@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this;
}
return With<AppClients>(id, client.Rename(newName), DeepComparer<AppClient>.Instance);
return With<AppClients>(id, client.Rename(newName));
}
[Pure]
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this;
}
return With<AppClients>(id, client.Update(role), DeepComparer<AppClient>.Instance);
return With<AppClients>(id, client.Update(role));
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppImage.cs

@ -9,6 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class AppImage
{
public string MimeType { get; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs

@ -10,6 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class AppPattern : Named
{
public string Pattern { get; }

2
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this;
}
return With<AppPatterns>(id, appPattern.Update(name, pattern, message), DeepComparer<AppPattern>.Instance);
return With<AppPatterns>(id, appPattern.Update(name, pattern, message));
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPlan.cs

@ -9,6 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class AppPlan
{
public RefToken Owner { get; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs

@ -12,6 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class LanguageConfig
{
public static readonly LanguageConfig Default = new LanguageConfig();

9
backend/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.Apps
}
[Pure]
public LanguagesConfig Set(Language language, bool isOptional = false, params Language[] fallbacks)
public LanguagesConfig Set(Language language, bool isOptional = false, params Language[]? fallbacks)
{
Guard.NotNull(language);
@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Core.Apps
Cleanup(newLanguages, ref newMaster);
if (newLanguages.EqualsDictionary(languages, EqualityComparer<string>.Default, DeepComparer<LanguageConfig>.Instance) && Equals(newMaster, master))
if (EqualLanguages(newLanguages) && Equals(newMaster, master))
{
return this;
}
@ -102,6 +102,11 @@ namespace Squidex.Domain.Apps.Core.Apps
return new LanguagesConfig(newLanguages, newMaster);
}
private bool EqualLanguages(Dictionary<string, LanguageConfig> newLanguages)
{
return newLanguages.EqualsDictionary(languages);
}
private void Cleanup(Dictionary<string, LanguageConfig> newLanguages, ref string newMaster)
{
if (!newLanguages.ContainsKey(newMaster))

2
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs

@ -15,6 +15,7 @@ using AllPermissions = Squidex.Shared.Permissions;
namespace Squidex.Domain.Apps.Core.Apps
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class Role : Named
{
public const string Editor = "Editor";
@ -24,6 +25,7 @@ namespace Squidex.Domain.Apps.Core.Apps
public PermissionSet Permissions { get; }
[IgnoreDuringEquals]
public bool IsDefault
{
get { return Roles.IsDefault(this); }

2
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs

@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this;
}
return Create(inner.With(name, role.Update(permissions), DeepComparer<Role>.Instance));
return Create(inner.With(name, role.Update(permissions)));
}
public static bool IsDefault(string role)

15
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/JsonWorkflowTransition.cs

@ -5,21 +5,20 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Contents.Json
{
public class JsonWorkflowTransition
{
[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; }
public string[]? Roles { get; set; }
public JsonWorkflowTransition()
{
@ -27,7 +26,9 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
public JsonWorkflowTransition(WorkflowTransition transition)
{
SimpleMapper.Map(transition, this);
Roles = transition.Roles?.ToArray();
Expression = transition.Expression;
}
public WorkflowTransition ToTransition()
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
roles = new[] { Role };
}
return new WorkflowTransition(Expression, roles);
return WorkflowTransition.When(Expression, roles);
}
}
}

19
backend/src/Squidex.Domain.Apps.Core.Model/Contents/NoUpdate.cs

@ -5,20 +5,35 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Contents
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class NoUpdate : WorkflowCondition
{
public static readonly NoUpdate Always = new NoUpdate(null, null);
public NoUpdate(string? expression, params string[]? roles)
public NoUpdate(string? expression, ReadOnlyCollection<string>? roles)
: base(expression, roles)
{
}
public static NoUpdate When(string? expression, params string[]? roles)
{
return new NoUpdate(expression, roles);
if (roles?.Length > 0)
{
return new NoUpdate(expression, ReadOnlyCollection.Create(roles));
}
else if (!string.IsNullOrWhiteSpace(expression))
{
return new NoUpdate(expression, null);
}
else
{
return Always;
}
}
}
}

8
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs

@ -7,11 +7,12 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Squidex.Domain.Apps.Core.Contents
{
[TypeConverter(typeof(StatusConverter))]
public struct Status : IEquatable<Status>
public struct Status : IEquatable<Status>, IComparable<Status>
{
public static readonly Status Archived = new Status("Archived");
public static readonly Status Draft = new Status("Draft");
@ -49,6 +50,11 @@ namespace Squidex.Domain.Apps.Core.Contents
return Name;
}
public int CompareTo([AllowNull] Status other)
{
return string.Compare(name, other.name, StringComparison.Ordinal);
}
public static bool operator ==(Status lhs, Status rhs)
{
return lhs.Equals(rhs);

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs

@ -8,9 +8,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Squidex.Infrastructure;
#pragma warning disable IDE0051 // Remove unused private members
namespace Squidex.Domain.Apps.Core.Contents
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class Workflow : Named
{
private const string DefaultName = "Unnamed";
@ -20,6 +24,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public static readonly Workflow Default = CreateDefault();
public static readonly Workflow Empty = new Workflow(default, EmptySteps);
[IgnoreDuringEquals]
public IReadOnlyDictionary<Status, WorkflowStep> Steps { get; } = EmptySteps;
public IReadOnlyList<Guid> SchemaIds { get; } = EmptySchemaIds;
@ -122,5 +127,17 @@ namespace Squidex.Domain.Apps.Core.Contents
{
return (Initial, Steps[Initial]);
}
[CustomEqualsInternal]
private bool CustomEquals(Workflow other)
{
return Steps.EqualsDictionary(other.Steps);
}
[CustomGetHashCode]
private int CustomHashCode()
{
return Steps.DictionaryHashCode();
}
}
}

15
backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowCondition.cs

@ -6,7 +6,6 @@
// ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Contents
{
@ -16,14 +15,18 @@ namespace Squidex.Domain.Apps.Core.Contents
public ReadOnlyCollection<string>? Roles { get; }
protected WorkflowCondition(string? expression, params string[]? roles)
protected WorkflowCondition(string? expression, ReadOnlyCollection<string>? roles)
{
Expression = expression;
if (roles != null)
{
Roles = ReadOnlyCollection.Create(roles);
}
Roles = roles;
}
public override string ToString()
{
var roles = Roles?.Count > 0 ? string.Join(", ", Roles) : "*";
return $"When=${Expression}, For={roles}";
}
}
}

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowStep.cs

@ -6,13 +6,18 @@
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
#pragma warning disable IDE0051 // Remove unused private members
namespace Squidex.Domain.Apps.Core.Contents
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class WorkflowStep
{
private static readonly IReadOnlyDictionary<Status, WorkflowTransition> EmptyTransitions = new Dictionary<Status, WorkflowTransition>();
[IgnoreDuringEquals]
public IReadOnlyDictionary<Status, WorkflowTransition> Transitions { get; }
public string? Color { get; }
@ -27,5 +32,17 @@ namespace Squidex.Domain.Apps.Core.Contents
NoUpdate = noUpdate;
}
[CustomEqualsInternal]
private bool CustomEquals(WorkflowStep other)
{
return Transitions.EqualsDictionary(other.Transitions);
}
[CustomGetHashCode]
private int CustomHashCode()
{
return Transitions.DictionaryHashCode();
}
}
}

19
backend/src/Squidex.Domain.Apps.Core.Model/Contents/WorkflowTransition.cs

@ -5,20 +5,35 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.ObjectModel;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Contents
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class WorkflowTransition : WorkflowCondition
{
public static readonly WorkflowTransition Always = new WorkflowTransition(null, null);
public WorkflowTransition(string? expression, params string[]? roles)
public WorkflowTransition(string? expression, ReadOnlyCollection<string>? roles)
: base(expression, roles)
{
}
public static WorkflowTransition When(string? expression, params string[]? roles)
{
return new WorkflowTransition(expression, roles);
if (roles?.Length > 0)
{
return new WorkflowTransition(expression, ReadOnlyCollection.Create(roles));
}
else if (!string.IsNullOrWhiteSpace(expression))
{
return new WorkflowTransition(expression, null);
}
else
{
return Always;
}
}
}
}

6
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{
Guard.NotNull(workflow);
return With<Workflows>(Guid.Empty, workflow, DeepComparer<Workflow>.Instance);
return With<Workflows>(Guid.Empty, workflow);
}
[Pure]
@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{
Guard.NotNull(workflow);
return With<Workflows>(id, workflow, DeepComparer<Workflow>.Instance);
return With<Workflows>(id, workflow);
}
[Pure]
@ -72,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Contents
return this;
}
return With<Workflows>(id, workflow, DeepComparer<Workflow>.Instance);
return With<Workflows>(id, workflow);
}
public Workflow GetFirst()

2
backend/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Freezable />
<Equals />
</Weavers>

1
backend/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd

@ -4,6 +4,7 @@
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Equals" minOccurs="0" maxOccurs="1" type="xs:anyType" />
<xs:element name="Freezable" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">

4
backend/src/Squidex.Domain.Apps.Core.Model/Freezable.cs

@ -7,13 +7,17 @@
using System;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core
{
[Equals(DoNotAddEquals = true, DoNotAddGetHashCode = true, DoNotAddEqualityOperators = true)]
public abstract class Freezable : IFreezable
{
private bool isFrozen;
[IgnoreEquals]
[IgnoreDuringEquals]
public bool IsFrozen
{
get { return isFrozen; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs

@ -8,7 +8,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using DeepEqual.Syntax;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Core.Rules
@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Rules
public bool DeepEquals(RuleAction action)
{
return this.WithDeepEqual(action).IgnoreProperty<Freezable>(x => x.IsFrozen).Compare();
return SimpleEquals.IsEquals(this, action);
}
}
}

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using DeepEqual.Syntax;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Rules
{
@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Rules
public bool DeepEquals(RuleTrigger action)
{
return this.WithDeepEqual(action).IgnoreProperty<Freezable>(x => x.IsFrozen).Compare();
return SimpleEquals.IsEquals(this, action);
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs

@ -9,6 +9,7 @@ using System;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class ArrayFieldProperties : FieldProperties
{
public int? MinItems { get; set; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class AssetsFieldProperties : FieldProperties
{
public bool MustBeImage { get; set; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs

@ -9,6 +9,7 @@ using NodaTime;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class DateTimeFieldProperties : FieldProperties
{
public Instant? MaxValue { get; set; }

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldNames.cs

@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using DeepEqual.Syntax;
namespace Squidex.Domain.Apps.Core.Schemas
{
@ -45,10 +44,5 @@ namespace Squidex.Domain.Apps.Core.Schemas
return new FieldNames(list);
}
public bool DeepEquals(FieldNames names)
{
return this.IsDeepEqual(names);
}
}
}

7
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs

@ -6,10 +6,10 @@
// ==========================================================================
using System.Collections.ObjectModel;
using DeepEqual.Syntax;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public abstract class FieldProperties : NamedElementPropertiesBase
{
public bool IsRequired { get; set; }
@ -27,10 +27,5 @@ namespace Squidex.Domain.Apps.Core.Schemas
public abstract RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings? settings = null);
public abstract NestedField CreateNestedField(long id, string name, IFieldSettings? settings = null);
public bool DeepEquals(FieldProperties properties)
{
return this.WithDeepEqual(properties).IgnoreProperty<Freezable>(x => x.IsFrozen).Compare();
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs

@ -7,6 +7,7 @@
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class GeolocationFieldProperties : FieldProperties
{
public GeolocationFieldEditor Editor { get; set; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs

@ -7,6 +7,7 @@
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class JsonFieldProperties : FieldProperties
{
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs

@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
typedProperties.Freeze();
if (properties.DeepEquals(typedProperties))
if (properties.Equals(typedProperties))
{
return this;
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs

@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class NumberFieldProperties : FieldProperties
{
public ReadOnlyCollection<double>? AllowedValues { get; set; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -12,6 +12,7 @@ using System.Linq;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class ReferencesFieldProperties : FieldProperties
{
public int? MinItems { get; set; }

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs

@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
var typedProperties = ValidateProperties(newProperties);
if (properties.DeepEquals(typedProperties))
if (properties.Equals(typedProperties))
{
return this;
}

6
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
newScripts ??= new SchemaScripts();
if (scripts.DeepEquals(newScripts))
if (scripts.Equals(newScripts))
{
return this;
}
@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
names ??= FieldNames.Empty;
if (fieldsInLists.DeepEquals(names))
if (fieldsInLists.SetEquals(names))
{
return this;
}
@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
names ??= FieldNames.Empty;
if (fieldsInReferences.DeepEquals(names))
if (fieldsInReferences.SetEquals(names))
{
return this;
}

5
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaProperties.cs

@ -6,17 +6,18 @@
// ==========================================================================
using System.Collections.ObjectModel;
using DeepEqual.Syntax;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class SchemaProperties : NamedElementPropertiesBase
{
public ReadOnlyCollection<string> Tags { get; set; }
public bool DeepEquals(SchemaProperties properties)
{
return this.WithDeepEqual(properties).IgnoreProperty<Freezable>(x => x.IsFrozen).Compare();
return SimpleEquals.IsEquals(this, properties);
}
}
}

8
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs

@ -5,10 +5,9 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using DeepEqual.Syntax;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators =true)]
public sealed class SchemaScripts : Freezable
{
public static readonly SchemaScripts Empty = new SchemaScripts();
@ -27,10 +26,5 @@ namespace Squidex.Domain.Apps.Core.Schemas
public string Delete { get; set; }
public string Query { get; set; }
public bool DeepEquals(SchemaScripts scripts)
{
return this.WithDeepEqual(scripts).IgnoreProperty<Freezable>(x => x.IsFrozen).Compare();
}
}
}

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs

@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class StringFieldProperties : FieldProperties
{
public ReadOnlyCollection<string>? AllowedValues { get; set; }

1
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/UIFieldProperties.cs

@ -7,6 +7,7 @@
namespace Squidex.Domain.Apps.Core.Schemas
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed class UIFieldProperties : FieldProperties
{
public UIFieldEditor Editor { get; set; }

8
backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -10,9 +10,13 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Fody" Version="4.2.1" PrivateAssets="all" />
<PackageReference Include="Equals.Fody" Version="1.9.5" />
<PackageReference Include="Fody" Version="4.2.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Freezable.Fody" Version="1.9.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="3.0.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.Collections.Immutable" Version="1.7.0" />

4
backend/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
yield return E(new SchemaCategoryChanged { Name = target.Category });
}
if (!source.Scripts.DeepEquals(target.Scripts))
if (!source.Scripts.Equals(target.Scripts))
{
yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts });
}
@ -128,7 +128,7 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
if (canUpdate(sourceField, targetField))
{
if (!sourceField.RawProperties.DeepEquals(targetField.RawProperties))
if (!sourceField.RawProperties.Equals(targetField.RawProperties))
{
yield return E(new FieldUpdated { FieldId = id, Properties = targetField.RawProperties });
}

7
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
@ -65,9 +66,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetCount = Collection.Find(filter).CountDocumentsAsync();
var assetItems =
Collection.Find(filter)
.AssetTake(query)
.AssetSkip(query)
.AssetSort(query)
.Take(query)
.Skip(query)
.Sort(query)
.ToListAsync();
await Task.WhenAll(assetItems, assetCount);

15
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -36,21 +36,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
return query;
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{
return cursor.Sort(query.BuildSort<MongoAssetEntity>());
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetTake(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{
return cursor.Take(query);
}
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSkip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{
return cursor.Skip(query);
}
public static FilterDefinition<MongoAssetEntity> BuildFilter(this ClrQuery query, Guid appId, Guid? parentId)
{
var filters = new List<FilterDefinition<MongoAssetEntity>>

270
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -1,270 +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 System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity>
{
private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, IAppProvider appProvider)
: base(database)
{
this.appProvider = appProvider;
this.serializer = serializer;
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{
return collection.Indexes.CreateManyAsync(new[]
{
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Ascending(x => x.Id)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Ascending(x => x.Id)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.Id)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt)
.Ascending(x => x.IsDeleted)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ReferencedIds))
}, ct);
}
protected override string CollectionName()
{
return "State_Contents";
}
public async Task<IResultList<IContentEntity>> QueryAsync(ISchemaEntity schema, ClrQuery query, List<Guid>? ids, Status[]? status, bool inDraft, bool includeDraft = true)
{
try
{
query = query.AdjustToModel(schema.SchemaDef, inDraft);
var filter = query.ToFilter(schema.Id, ids, status);
var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems =
Collection.Find(filter)
.WithoutDraft(includeDraft)
.ContentTake(query)
.ContentSkip(query)
.ContentSort(query)
.ToListAsync();
await Task.WhenAll(contentItems, contentCount);
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef, serializer);
}
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
}
catch (MongoQueryException ex)
{
if (ex.Message.Contains("17406"))
{
throw new DomainException("Result set is too large to be retrieved. Use $top parameter to reduce the number of items.");
}
else
{
throw;
}
}
}
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<Guid> ids, Status[]? status, bool includeDraft)
{
var find = Collection.Find(FilterFactory.IdsByApp(app.Id, ids, status));
var contentItems = await find.WithoutDraft(includeDraft).ToListAsync();
var schemaIds = contentItems.Select(x => x.IndexedSchemaId).ToList();
var schemas = await Task.WhenAll(schemaIds.Select(x => appProvider.GetSchemaAsync(app.Id, x)));
var result = new List<(IContentEntity Content, ISchemaEntity Schema)>();
foreach (var entity in contentItems)
{
var schema = schemas.FirstOrDefault(x => x?.Id == entity.IndexedSchemaId);
if (schema != null)
{
entity.ParseData(schema.SchemaDef, serializer);
result.Add((entity, schema));
}
}
return result;
}
public async Task<IResultList<IContentEntity>> QueryAsync(ISchemaEntity schema, HashSet<Guid> ids, Status[]? status, bool includeDraft)
{
var find = Collection.Find(FilterFactory.IdsBySchema(schema.Id, ids, status));
var contentItems = await find.WithoutDraft(includeDraft).ToListAsync();
foreach (var entity in contentItems)
{
entity.ParseData(schema.SchemaDef, serializer);
}
return ResultList.Create<IContentEntity>(contentItems.Count, contentItems);
}
public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, Guid id, Status[]? status, bool includeDraft)
{
var find = Collection.Find(x => x.Id == id);
var contentEntity = await find.WithoutDraft(includeDraft).FirstOrDefaultAsync();
if (contentEntity != null)
{
if (contentEntity.IndexedSchemaId != schema.Id || status?.Contains(contentEntity.Status) == false)
{
return null;
}
contentEntity?.ParseData(schema.SchemaDef, serializer);
}
return contentEntity;
}
public Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback)
{
return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true)
.Not(x => x.DataByIds)
.Not(x => x.DataDraftByIds)
.ForEachAsync(c =>
{
callback(c);
});
}
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(ISchemaEntity schema, FilterNode<ClrValue> filterNode)
{
var filter = filterNode.AdjustToModel(schema.SchemaDef, true)?.ToFilter(schema.Id);
var contentEntities =
await Collection.Find(filter).Only(x => x.Id, x => x.IndexedSchemaId)
.ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x["_si"].AsString), Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, HashSet<Guid> ids)
{
var contentEntities =
await Collection.Find(Filter.And(Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.Id, ids))).Only(x => x.Id, x => x.IndexedSchemaId)
.ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x["_si"].AsString), Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId)
{
var contentEntities =
await Collection.Find(x => x.IndexedAppId == appId).Only(x => x.Id)
.ToListAsync();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func<Guid, Guid, Task<ISchemaEntity>> getSchema)
{
var contentEntity =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null!, EtagVersion.NotFound);
}
public Task ReadAllAsync(Func<ContentState, long, Task> callback, Func<Guid, Guid, Task<ISchemaEntity>> getSchema, CancellationToken ct = default)
{
return Collection.Find(new BsonDocument(), options: Batching.Options).ForEachPipelineAsync(async contentEntity =>
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, serializer);
await callback(SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}, ct);
}
public Task CleanupAsync(Guid id)
{
return Collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, id),
Filter.AnyNe(x => x.ReferencedIdsDeleted, id)),
Update.AddToSet(x => x.ReferencedIdsDeleted, id));
}
public Task RemoveAsync(Guid id)
{
return Collection.DeleteOneAsync(x => x.Id == id);
}
public Task UpsertAsync(MongoContentEntity content, long oldVersion)
{
return Collection.UpsertVersionedAsync(content.Id, oldVersion, content);
}
}
}

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

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -16,26 +17,31 @@ using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public partial class MongoContentRepository : IContentRepository, IInitializable
public partial class MongoContentRepository : MongoRepositoryBase<MongoContentEntity>, IContentRepository
{
private static readonly List<(Guid SchemaId, Guid Id)> EmptyIds = new List<(Guid SchemaId, Guid Id)>();
private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
private readonly ITextIndexer indexer;
private readonly string typeAssetDeleted;
private readonly string typeContentDeleted;
private readonly MongoContentCollection contents;
private readonly CleanupReferences cleanupReferences;
private readonly QueryContent queryContentAsync;
private readonly QueryContentsByIds queryContentsById;
private readonly QueryContentsByQuery queryContentsByQuery;
private readonly QueryIdsAsync queryIdsAsync;
private readonly QueryScheduledContents queryScheduledItems;
static MongoContentRepository()
{
@ -43,77 +49,79 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, ITextIndexer indexer, TypeNameRegistry typeNameRegistry)
: base(database)
{
Guard.NotNull(appProvider);
Guard.NotNull(serializer);
Guard.NotNull(indexer);
Guard.NotNull(typeNameRegistry);
this.appProvider = appProvider;
this.indexer = indexer;
this.serializer = serializer;
cleanupReferences = new CleanupReferences();
queryContentAsync = new QueryContent(serializer);
queryContentsById = new QueryContentsByIds(serializer, appProvider);
queryContentsByQuery = new QueryContentsByQuery(serializer, indexer);
queryIdsAsync = new QueryIdsAsync(appProvider);
queryScheduledItems = new QueryScheduledContents();
typeAssetDeleted = typeNameRegistry.GetName<AssetDeleted>();
typeContentDeleted = typeNameRegistry.GetName<ContentDeleted>();
}
contents = new MongoContentCollection(database, serializer, appProvider);
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{
await cleanupReferences.PrepareAsync(collection, ct);
await queryContentAsync.PrepareAsync(collection, ct);
await queryContentsById.PrepareAsync(collection, ct);
await queryContentsByQuery.PrepareAsync(collection, ct);
await queryIdsAsync.PrepareAsync(collection, ct);
await queryScheduledItems.PrepareAsync(collection, ct);
}
public Task InitializeAsync(CancellationToken ct = default)
protected override string CollectionName()
{
return contents.InitializeAsync(ct);
return "State_Contents";
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[]? status, bool inDraft, ClrQuery query, bool includeDraft = true)
{
Guard.NotNull(app);
Guard.NotNull(schema);
Guard.NotNull(query);
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByQuery"))
{
var fullTextIds = await indexer.SearchAsync(query.FullText, app, schema.Id, inDraft ? Scope.Draft : Scope.Published);
if (fullTextIds?.Count == 0)
{
return ResultList.CreateFrom<IContentEntity>(0);
}
return await contents.QueryAsync(schema, query, fullTextIds, status, inDraft, includeDraft);
return await queryContentsByQuery.DoAsync(app, schema, query, status, inDraft, includeDraft);
}
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[]? status, HashSet<Guid> ids, bool includeDraft = true)
{
Guard.NotNull(app);
Guard.NotNull(ids);
Guard.NotNull(schema);
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIds"))
{
return await contents.QueryAsync(schema, ids, status, includeDraft);
var result = await queryContentsById.DoAsync(app.Id, schema, ids, status, includeDraft);
return ResultList.Create(result.Count, result.Select(x => x.Content));
}
}
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, Status[]? status, HashSet<Guid> ids, bool includeDraft = true)
{
Guard.NotNull(app);
Guard.NotNull(ids);
using (Profiler.TraceMethod<MongoContentRepository>("QueryAsyncByIdsWithoutSchema"))
{
return await contents.QueryAsync(app, ids, status, includeDraft);
var result = await queryContentsById.DoAsync(app.Id, null, ids, status, includeDraft);
return result;
}
}
public async Task<IContentEntity?> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[]? status, Guid id, bool includeDraft = true)
{
Guard.NotNull(app);
Guard.NotNull(schema);
using (Profiler.TraceMethod<MongoContentRepository>())
{
return await contents.FindContentAsync(schema, id, status, includeDraft);
return await queryContentAsync.DoAsync(schema, id, status, includeDraft);
}
}
@ -121,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
await contents.QueryScheduledWithoutDataAsync(now, callback);
await queryScheduledItems.DoAsync(now, callback);
}
}
@ -129,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
return await contents.QueryIdsAsync(appId, ids);
return await queryIdsAsync.DoAsync(appId, ids);
}
}
@ -137,20 +145,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
var schema = await appProvider.GetSchemaAsync(appId, schemaId);
if (schema == null)
{
return EmptyIds;
}
return await contents.QueryIdsAsync(schema, filterNode);
return await queryIdsAsync.DoAsync(appId, schemaId, filterNode);
}
}
public Task ClearAsync()
{
return contents.ClearAsync();
}
}
}

4
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -35,10 +35,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
switch (@event.Payload)
{
case AssetDeleted e:
return contents.CleanupAsync(e.AssetId);
return cleanupReferences.DoAsync(e.AssetId);
case ContentDeleted e:
return contents.CleanupAsync(e.ContentId);
return cleanupReferences.DoAsync(e.ContentId);
}
return TaskHelper.Done;

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

@ -8,10 +8,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
@ -19,19 +21,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public partial class MongoContentRepository : ISnapshotStore<ContentState, Guid>
{
async Task ISnapshotStore<ContentState, Guid>.RemoveAsync(Guid key)
Task ISnapshotStore<ContentState, Guid>.ReadAllAsync(Func<ContentState, long, Task> callback, CancellationToken ct)
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
await contents.RemoveAsync(key);
}
throw new NotSupportedException();
}
async Task ISnapshotStore<ContentState, Guid>.ReadAllAsync(Func<ContentState, long, Task> callback, CancellationToken ct)
async Task ISnapshotStore<ContentState, Guid>.RemoveAsync(Guid key)
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
await contents.ReadAllAsync(callback, GetSchemaAsync, ct);
await Collection.DeleteOneAsync(x => x.Id == key);
}
}
@ -39,7 +38,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
using (Profiler.TraceMethod<MongoContentRepository>())
{
return await contents.ReadAsync(key, GetSchemaAsync);
var contentEntity =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await GetSchemaAsync(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null!, EtagVersion.NotFound);
}
}
@ -74,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
Version = newVersion
});
await contents.UpsertAsync(content, oldVersion);
await Collection.UpsertVersionedAsync(content.Id, oldVersion, content);
}
}

39
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/Adapt.cs → backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs

@ -10,17 +10,31 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
public static class Adapt
{
private static readonly Dictionary<string, string> PropertyMap =
typeof(MongoContentEntity).GetProperties()
.ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase);
.ToDictionary(
x => ToElementName(x),
x => ToName(x),
StringComparer.OrdinalIgnoreCase);
private static string ToName(PropertyInfo x)
{
return x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name;
}
private static string ToElementName(PropertyInfo x)
{
return x.Name;
}
public static Func<PropertyPath, PropertyPath> Path(Schema schema, bool inDraft)
{
@ -79,5 +93,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return result;
};
}
public static ClrQuery AdjustToModel(this ClrQuery query, Schema schema, bool useDraft)
{
var pathConverter = Path(schema, useDraft);
if (query.Filter != null)
{
query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
}
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.Order)).ToList();
return query;
}
public static FilterNode<ClrValue>? AdjustToModel(this FilterNode<ClrValue> filterNode, Schema schema, bool useDraft)
{
var pathConverter = Path(schema, useDraft);
return filterNode.Accept(new AdaptionVisitor(pathConverter));
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs → backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/AdaptionVisitor.cs

@ -11,7 +11,7 @@ using System.Linq;
using NodaTime;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class AdaptionVisitor : TransformVisitor<ClrValue>
{

35
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/CleanupReferences.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.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class CleanupReferences : OperationBase
{
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index =
new CreateIndexModel<MongoContentEntity>(
Index.Ascending(x => x.ReferencedIds));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
}
public Task DoAsync(Guid id)
{
return Collection.UpdateManyAsync(
Filter.And(
Filter.AnyEq(x => x.ReferencedIds, id),
Filter.AnyNe(x => x.ReferencedIdsDeleted, id)),
Update.AddToSet(x => x.ReferencedIdsDeleted, id));
}
}
}

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs → backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs

@ -8,11 +8,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
@ -42,5 +44,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson(serializer)));
}
public static bool HasStatus(this MongoContentEntity content, Status[]? status)
{
return status == null || status.Contains(content.Status);
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> WithoutDraft(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, bool includeDraft)
{
return !includeDraft ? cursor.Not(x => x.DataDraftByIds, x => x.IsDeleted) : cursor;
}
}
}

39
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs

@ -0,0 +1,39 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
public abstract class OperationBase
{
protected static readonly SortDefinitionBuilder<MongoContentEntity> Sort = Builders<MongoContentEntity>.Sort;
protected static readonly UpdateDefinitionBuilder<MongoContentEntity> Update = Builders<MongoContentEntity>.Update;
protected static readonly FieldDefinitionBuilder<MongoContentEntity> Fields = FieldDefinitionBuilder<MongoContentEntity>.Instance;
protected static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
protected static readonly IndexKeysDefinitionBuilder<MongoContentEntity> Index = Builders<MongoContentEntity>.IndexKeys;
protected static readonly ProjectionDefinitionBuilder<MongoContentEntity> Projection = Builders<MongoContentEntity>.Projection;
public IMongoCollection<MongoContentEntity> Collection { get; private set; }
public Task PrepareAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{
Collection = collection;
return PrepareAsync(ct);
}
protected virtual Task PrepareAsync(CancellationToken ct = default)
{
return TaskHelper.Done;
}
}
}

49
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContent.cs

@ -0,0 +1,49 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryContent : OperationBase
{
private readonly IJsonSerializer serializer;
public QueryContent(IJsonSerializer serializer)
{
this.serializer = serializer;
}
public async Task<IContentEntity?> DoAsync(ISchemaEntity schema, Guid id, Status[]? status, bool includeDraft)
{
Guard.NotNull(schema);
var find = Collection.Find(x => x.Id == id).WithoutDraft(includeDraft);
var contentEntity = await find.FirstOrDefaultAsync();
if (contentEntity != null)
{
if (contentEntity.IndexedSchemaId != schema.Id || !contentEntity.HasStatus(status))
{
return null;
}
contentEntity?.ParseData(schema.SchemaDef, serializer);
}
return contentEntity;
}
}
}

128
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs

@ -0,0 +1,128 @@
// ==========================================================================
// 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 System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryContentsByIds : OperationBase
{
private readonly IJsonSerializer serializer;
private readonly IAppProvider appProvider;
public QueryContentsByIds(IJsonSerializer serializer, IAppProvider appProvider)
{
this.serializer = serializer;
this.appProvider = appProvider;
}
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
}
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> DoAsync(Guid appId, ISchemaEntity? schema, HashSet<Guid> ids, Status[]? status, bool includeDraft)
{
Guard.NotNull(ids);
var find = Collection.Find(CreateFilter(appId, ids, status)).WithoutDraft(includeDraft);
var contentItems = await find.ToListAsync();
var contentSchemas = await GetSchemasAsync(appId, schema, contentItems);
var result = new List<(IContentEntity Content, ISchemaEntity Schema)>();
foreach (var contentEntity in contentItems)
{
if (contentEntity.HasStatus(status) && contentSchemas.TryGetValue(contentEntity.IndexedSchemaId, out var contentSchema))
{
contentEntity.ParseData(contentSchema.SchemaDef, serializer);
result.Add((contentEntity, contentSchema));
}
}
return result;
}
private async Task<IDictionary<Guid, ISchemaEntity>> GetSchemasAsync(Guid appId, ISchemaEntity? schema, List<MongoContentEntity> contentItems)
{
var schemas = new Dictionary<Guid, ISchemaEntity>();
if (schema != null)
{
schemas[schema.Id] = schema;
}
var misingSchemaIds = contentItems.Select(x => x.IndexedSchemaId).Distinct().Where(x => !schemas.ContainsKey(x));
var missingSchemas = await Task.WhenAll(misingSchemaIds.Select(x => appProvider.GetSchemaAsync(appId, x)));
foreach (var missingSchema in missingSchemas)
{
schemas[missingSchema.Id] = missingSchema;
}
return schemas;
}
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid appId, ICollection<Guid> ids, Status[]? status)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedAppId, appId),
Filter.Ne(x => x.IsDeleted, true)
};
if (status != null)
{
filters.Add(Filter.In(x => x.Status, status));
}
else
{
filters.Add(Filter.Exists(x => x.Status));
}
if (ids != null && ids.Count > 0)
{
if (ids.Count > 1)
{
filters.Add(
Filter.Or(
Filter.In(x => x.Id, ids)));
}
else
{
var first = ids.First();
filters.Add(
Filter.Or(
Filter.Eq(x => x.Id, first)));
}
}
return Filter.And(filters);
}
}
}

159
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs

@ -0,0 +1,159 @@
// ==========================================================================
// 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 System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryContentsByQuery : OperationBase
{
private readonly IJsonSerializer serializer;
private readonly ITextIndexer indexer;
public QueryContentsByQuery(IJsonSerializer serializer, ITextIndexer indexer)
{
this.serializer = serializer;
this.indexer = indexer;
}
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index1 =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Ascending(x => x.Id)
.Ascending(x => x.ReferencedIds)
.Descending(x => x.LastModified));
var index2 =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)
.Descending(x => x.LastModified));
return Collection.Indexes.CreateManyAsync(new[] { index1, index2, }, ct);
}
public async Task<IResultList<IContentEntity>> DoAsync(IAppEntity app, ISchemaEntity schema, ClrQuery query, Status[]? status, bool inDraft, bool includeDraft = true)
{
Guard.NotNull(app);
Guard.NotNull(schema);
Guard.NotNull(query);
try
{
query = query.AdjustToModel(schema.SchemaDef, inDraft);
List<Guid>? fullTextIds = null;
if (!string.IsNullOrWhiteSpace(query.FullText))
{
fullTextIds = await indexer.SearchAsync(query.FullText, app, schema.Id, inDraft ? Scope.Draft : Scope.Published);
if (fullTextIds?.Count == 0)
{
return ResultList.CreateFrom<IContentEntity>(0);
}
}
var filter = CreateFilter(schema.Id, fullTextIds, status, query);
var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems =
Collection.Find(filter)
.WithoutDraft(includeDraft)
.Take(query)
.Skip(query)
.Sort(query)
.ToListAsync();
await Task.WhenAll(contentItems, contentCount);
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef, serializer);
}
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
}
catch (MongoQueryException ex)
{
if (ex.Message.Contains("17406"))
{
throw new DomainException("Result set is too large to be retrieved. Use $top parameter to reduce the number of items.");
}
else
{
throw;
}
}
}
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid schemaId, ICollection<Guid>? ids, Status[]? status, ClrQuery? query)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true)
};
if (status != null)
{
filters.Add(Filter.In(x => x.Status, status));
}
else
{
filters.Add(Filter.Exists(x => x.Status));
}
if (ids != null && ids.Count > 0)
{
if (ids.Count > 1)
{
filters.Add(
Filter.Or(
Filter.In(x => x.Id, ids),
Filter.AnyIn(x => x.ReferencedIds, ids)));
}
else
{
var first = ids.First();
filters.Add(
Filter.Or(
Filter.Eq(x => x.Id, first),
Filter.AnyEq(x => x.ReferencedIds, first)));
}
}
if (query?.Filter != null)
{
filters.Add(query.Filter.BuildFilter<MongoContentEntity>());
}
return Filter.And(filters);
}
}
}

88
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs

@ -0,0 +1,88 @@
// ==========================================================================
// 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 System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryIdsAsync : OperationBase
{
private static readonly List<(Guid SchemaId, Guid Id)> EmptyIds = new List<(Guid SchemaId, Guid Id)>();
private readonly IAppProvider appProvider;
public QueryIdsAsync(IAppProvider appProvider)
{
this.appProvider = appProvider;
}
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index1 =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.Id)
.Ascending(x => x.ReferencedIds));
var index2 =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId));
return Collection.Indexes.CreateManyAsync(new[] { index1, index2, }, ct);
}
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, HashSet<Guid> ids)
{
var contentEntities =
await Collection.Find(Filter.And(Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.Id, ids))).Only(x => x.Id, x => x.IndexedSchemaId)
.ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x["_si"].AsString), Guid.Parse(x["_id"].AsString))).ToList();
}
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode)
{
var schema = await appProvider.GetSchemaAsync(appId, schemaId);
if (schema == null)
{
return EmptyIds;
}
var filter = BuildFilter(filterNode.AdjustToModel(schema.SchemaDef, true), schemaId);
var contentEntities =
await Collection.Find(filter).Only(x => x.Id, x => x.IndexedSchemaId)
.ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x["_si"].AsString), Guid.Parse(x["_id"].AsString))).ToList();
}
public static FilterDefinition<MongoContentEntity> BuildFilter(FilterNode<ClrValue>? filterNode, Guid schemaId)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true),
};
if (filterNode != null)
{
filters.Add(filterNode.BuildFilter<MongoContentEntity>());
}
return Filter.And(filters);
}
}
}

44
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduledContents.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryScheduledContents : OperationBase
{
protected override Task PrepareAsync(CancellationToken ct = default)
{
var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt)
.Ascending(x => x.IsDeleted));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
}
public Task DoAsync(Instant now, Func<IContentEntity, Task> callback)
{
Guard.NotNull(callback);
return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true)
.Not(x => x.DataByIds)
.Not(x => x.DataDraftByIds)
.ForEachAsync(c =>
{
callback(c);
});
}
}
}

134
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs

@ -1,134 +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 MongoDB.Driver;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{
public static class FilterFactory
{
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
public static ClrQuery AdjustToModel(this ClrQuery query, Schema schema, bool useDraft)
{
var pathConverter = Adapt.Path(schema, useDraft);
if (query.Filter != null)
{
query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
}
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.Order)).ToList();
return query;
}
public static FilterNode<ClrValue>? AdjustToModel(this FilterNode<ClrValue> filterNode, Schema schema, bool useDraft)
{
var pathConverter = Adapt.Path(schema, useDraft);
return filterNode.Accept(new AdaptionVisitor(pathConverter));
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{
return cursor.Sort(query.BuildSort<MongoContentEntity>());
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentTake(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{
return cursor.Take(query);
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSkip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{
return cursor.Skip(query);
}
public static IFindFluent<MongoContentEntity, MongoContentEntity> WithoutDraft(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, bool includeDraft)
{
return !includeDraft ? cursor.Not(x => x.DataDraftByIds, x => x.IsDeleted) : cursor;
}
public static FilterDefinition<MongoContentEntity> IdsByApp(Guid appId, ICollection<Guid> ids, Status[]? status)
{
return CreateFilter(appId, null, ids, status, null);
}
public static FilterDefinition<MongoContentEntity> IdsBySchema(Guid schemaId, ICollection<Guid> ids, Status[]? status)
{
return CreateFilter(null, schemaId, ids, status, null);
}
public static FilterDefinition<MongoContentEntity> ToFilter(this ClrQuery query, Guid schemaId, ICollection<Guid>? ids, Status[]? status)
{
return CreateFilter(null, schemaId, ids, status, query);
}
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid? appId, Guid? schemaId, ICollection<Guid>? ids, Status[]? status,
ClrQuery? query)
{
var filters = new List<FilterDefinition<MongoContentEntity>>();
if (appId.HasValue)
{
filters.Add(Filter.Eq(x => x.IndexedAppId, appId.Value));
}
if (schemaId.HasValue)
{
filters.Add(Filter.Eq(x => x.IndexedSchemaId, schemaId.Value));
}
filters.Add(Filter.Ne(x => x.IsDeleted, true));
if (status != null)
{
filters.Add(Filter.In(x => x.Status, status));
}
if (ids != null && ids.Count > 0)
{
if (ids.Count > 1)
{
filters.Add(Filter.In(x => x.Id, ids));
}
else
{
filters.Add(Filter.Eq(x => x.Id, ids.First()));
}
}
if (query?.Filter != null)
{
filters.Add(query.Filter.BuildFilter<MongoContentEntity>());
}
return Filter.And(filters);
}
public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode<ClrValue> filterNode, Guid schemaId)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true),
filterNode.BuildFilter<MongoContentEntity>()
};
return Filter.And(filters);
}
}
}

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

@ -29,11 +29,19 @@ namespace Squidex.Infrastructure.MongoDb
protected static readonly ProjectionDefinitionBuilder<TEntity> Projection = Builders<TEntity>.Projection;
private readonly IMongoDatabase mongoDatabase;
private readonly Lazy<IMongoCollection<TEntity>> mongoCollection;
private IMongoCollection<TEntity> mongoCollection;
protected IMongoCollection<TEntity> Collection
{
get { return mongoCollection.Value; }
get
{
if (mongoCollection == null)
{
throw new InvalidOperationException("Collection has not been initialized yet.");
}
return mongoCollection;
}
}
protected IMongoDatabase Database
@ -48,12 +56,16 @@ namespace Squidex.Infrastructure.MongoDb
InstantSerializer.Register();
}
protected MongoRepositoryBase(IMongoDatabase database)
protected MongoRepositoryBase(IMongoDatabase database, bool setup = false)
{
Guard.NotNull(database);
mongoDatabase = database;
mongoCollection = CreateCollection();
if (setup)
{
CreateCollection();
}
}
protected virtual MongoCollectionSettings CollectionSettings()
@ -66,14 +78,6 @@ namespace Squidex.Infrastructure.MongoDb
return string.Format(CultureInfo.InvariantCulture, CollectionFormat, typeof(TEntity).Name);
}
private Lazy<IMongoCollection<TEntity>> CreateCollection()
{
return new Lazy<IMongoCollection<TEntity>>(() =>
mongoDatabase.GetCollection<TEntity>(
CollectionName(),
CollectionSettings() ?? new MongoCollectionSettings()));
}
protected virtual Task SetupCollectionAsync(IMongoCollection<TEntity> collection, CancellationToken ct = default)
{
return TaskHelper.Done;
@ -81,15 +85,27 @@ namespace Squidex.Infrastructure.MongoDb
public virtual async Task ClearAsync()
{
await Database.DropCollectionAsync(CollectionName());
try
{
await Database.DropCollectionAsync(CollectionName());
}
catch (MongoCommandException ex)
{
if (ex.Code != 26)
{
throw;
}
}
await SetupCollectionAsync(Collection);
await InitializeAsync();
}
public async Task InitializeAsync(CancellationToken ct = default)
{
try
{
CreateCollection();
await SetupCollectionAsync(Collection, ct);
}
catch (Exception ex)
@ -97,5 +113,12 @@ namespace Squidex.Infrastructure.MongoDb
throw new ConfigurationException($"MongoDb connection failed to connect to database {Database.DatabaseNamespace.DatabaseName}", ex);
}
}
private void CreateCollection()
{
mongoCollection = mongoDatabase.GetCollection<TEntity>(
CollectionName(),
CollectionSettings() ?? new MongoCollectionSettings());
}
}
}

5
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs

@ -31,5 +31,10 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return cursor;
}
public static IFindFluent<T, T> Sort<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{
return cursor.Sort(query.BuildSort<T>());
}
}
}

8
backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs

@ -20,11 +20,17 @@ namespace Squidex.Infrastructure.States
public class MongoSnapshotStore<T, TKey> : MongoRepositoryBase<MongoState<T, TKey>>, ISnapshotStore<T, TKey> where TKey : notnull
{
public MongoSnapshotStore(IMongoDatabase database, JsonSerializer jsonSerializer)
: base(database)
: base(database, Register(jsonSerializer))
{
}
private static bool Register(JsonSerializer jsonSerializer)
{
Guard.NotNull(jsonSerializer);
BsonJsonConvention.Register(jsonSerializer);
return true;
}
protected override string CollectionName()

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

@ -138,12 +138,12 @@ namespace Squidex.Infrastructure
return hashCode;
}
public static int DictionaryHashCode<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) where TKey : notnull
public static int DictionaryHashCode<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary) where TKey : notnull
{
return DictionaryHashCode(dictionary, EqualityComparer<TKey>.Default, EqualityComparer<TValue>.Default);
}
public static int DictionaryHashCode<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) where TKey : notnull
public static int DictionaryHashCode<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) where TKey : notnull
{
var hashCode = 17;

4
backend/src/Squidex.Infrastructure/Collections/ImmutableDictionary{TKey,TValue}.cs

@ -81,7 +81,9 @@ namespace Squidex.Infrastructure.Collections
private static bool IsEqual(TValue lhs, TValue rhs, IEqualityComparer<TValue>? comparer = null)
{
return comparer == null || comparer.Equals(lhs, rhs);
comparer ??= EqualityComparer<TValue>.Default;
return comparer.Equals(lhs, rhs);
}
public ImmutableDictionary<TKey, TValue> Without(TKey key)

3
backend/src/Squidex.Infrastructure/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Equals />
</Weavers>

26
backend/src/Squidex.Infrastructure/FodyWeavers.xsd

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Equals" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
backend/src/Squidex.Infrastructure/Guard.cs

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -74,7 +73,7 @@ namespace Squidex.Infrastructure
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void HasType(object? target, [AllowNull] Type expectedType, [CallerArgumentExpression("target")] string? parameterName = null)
public static void HasType(object? target, Type? expectedType, [CallerArgumentExpression("target")] string? parameterName = null)
{
if (target != null && expectedType != null && target.GetType() != expectedType)
{

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

@ -12,6 +12,7 @@ using System.Text.RegularExpressions;
namespace Squidex.Infrastructure
{
[Equals(DoNotAddEqualityOperators = true)]
public sealed partial class Language
{
private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase);
@ -41,6 +42,7 @@ namespace Squidex.Infrastructure
get { return AllLanguagesField.Values; }
}
[IgnoreDuringEquals]
public string EnglishName { get; }
public string Iso2Code { get; }

18
backend/src/Squidex.Infrastructure/NamedId{T}.cs

@ -14,7 +14,8 @@ namespace Squidex.Infrastructure
{
public delegate bool Parser<T>(string input, out T result);
public sealed class NamedId<T> : IEquatable<NamedId<T>> where T : notnull
[Equals(DoNotAddEqualityOperators = true)]
public sealed class NamedId<T> where T : notnull
{
private static readonly int GuidLength = Guid.Empty.ToString().Length;
@ -37,21 +38,6 @@ namespace Squidex.Infrastructure
return $"{Id},{Name}";
}
public override bool Equals(object? obj)
{
return Equals(obj as NamedId<T>);
}
public bool Equals(NamedId<T>? other)
{
return other != null && (ReferenceEquals(this, other) || (Id.Equals(other.Id) && Name.Equals(other.Name)));
}
public override int GetHashCode()
{
return (Id.GetHashCode() * 397) ^ Name.GetHashCode();
}
public static bool TryParse(string value, Parser<T> parser, [MaybeNullWhen(false)] out NamedId<T> result)
{
if (value != null)

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

@ -10,17 +10,20 @@ using System.Diagnostics.CodeAnalysis;
namespace Squidex.Infrastructure
{
public sealed class RefToken : IEquatable<RefToken>
[Equals(DoNotAddEqualityOperators = true)]
public sealed class RefToken
{
public string Type { get; }
public string Identifier { get; }
[IgnoreDuringEquals]
public bool IsClient
{
get { return string.Equals(Type, RefTokenType.Client, StringComparison.OrdinalIgnoreCase); }
}
[IgnoreDuringEquals]
public bool IsSubject
{
get { return string.Equals(Type, RefTokenType.Subject, StringComparison.OrdinalIgnoreCase); }
@ -41,16 +44,6 @@ namespace Squidex.Infrastructure
return $"{Type}:{Identifier}";
}
public override bool Equals(object? obj)
{
return Equals(obj as RefToken);
}
public bool Equals(RefToken? other)
{
return other != null && (ReferenceEquals(this, other) || (Type.Equals(other.Type) && Identifier.Equals(other.Identifier)));
}
public override int GetHashCode()
{
return (Type.GetHashCode() * 397) ^ Identifier.GetHashCode();

40
backend/src/Squidex.Infrastructure/Reflection/Equality/ArrayComparer.cs

@ -0,0 +1,40 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class ArrayComparer<T> : IDeepComparer
{
private readonly IDeepComparer itemComparer;
public ArrayComparer(IDeepComparer itemComparer)
{
this.itemComparer = itemComparer;
}
public bool IsEquals(object? x, object? y)
{
var lhs = (T[])x!;
var rhs = (T[])y!;
if (lhs.Length != rhs.Length)
{
return false;
}
for (var i = 0; i < lhs.Length; i++)
{
if (!itemComparer.IsEquals(lhs[i], rhs[i]))
{
return false;
}
}
return true;
}
}
}

69
backend/src/Squidex.Infrastructure/Reflection/Equality/CollectionComparer.cs

@ -0,0 +1,69 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections;
using Squidex.Infrastructure.Reflection.Internal;
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class CollectionComparer : IDeepComparer
{
private readonly IDeepComparer itemComparer;
private readonly PropertyAccessor? sizeProperty;
public CollectionComparer(IDeepComparer itemComparer, PropertyAccessor? sizeProperty)
{
this.itemComparer = itemComparer;
this.sizeProperty = sizeProperty;
}
public bool IsEquals(object? x, object? y)
{
var lhs = (IEnumerable)x!;
var rhs = (IEnumerable)y!;
if (sizeProperty != null)
{
var sizeLhs = sizeProperty.Get(lhs);
var sizeRhs = sizeProperty.Get(rhs);
if (!Equals(sizeLhs, sizeRhs))
{
return false;
}
}
var enumeratorLhs = lhs.GetEnumerator();
var enumeratorRhs = rhs.GetEnumerator();
while (true)
{
var movedLhs = enumeratorLhs.MoveNext();
var movedRhs = enumeratorRhs.MoveNext();
if (movedRhs != movedLhs)
{
return false;
}
if (movedRhs)
{
if (!itemComparer.IsEquals(enumeratorLhs.Current, enumeratorRhs.Current))
{
return false;
}
}
else
{
break;
}
}
return true;
}
}
}

13
backend/src/Squidex.Domain.Apps.Core.Model/DeepComparer.cs → backend/src/Squidex.Infrastructure/Reflection/Equality/DeepEqualityComparer.cs

@ -6,21 +6,22 @@
// ==========================================================================
using System.Collections.Generic;
using DeepEqual.Syntax;
namespace Squidex.Domain.Apps.Core
namespace Squidex.Infrastructure.Reflection.Equality
{
public sealed class DeepComparer<T> : IEqualityComparer<T>
public sealed class DeepEqualityComparer<T> : IEqualityComparer<T>
{
public static readonly DeepComparer<T> Instance = new DeepComparer<T>();
public static readonly DeepEqualityComparer<T> Default = new DeepEqualityComparer<T>();
private readonly IDeepComparer comparer;
private DeepComparer()
public DeepEqualityComparer(IDeepComparer? comparer = null)
{
this.comparer = comparer ?? SimpleEquals.Build(typeof(T));
}
public bool Equals(T x, T y)
{
return x.IsDeepEqual(y);
return comparer.IsEquals(x, y);
}
public int GetHashCode(T obj)

36
backend/src/Squidex.Infrastructure/Reflection/Equality/DefaultComparer.cs

@ -0,0 +1,36 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class DefaultComparer : IDeepComparer
{
public bool IsEquals(object? x, object? y)
{
if (Equals(x, y))
{
return true;
}
if (x == null || y == null)
{
return false;
}
var type = x.GetType();
if (type != y.GetType())
{
return false;
}
var inner = SimpleEquals.BuildInner(type);
return inner.IsEquals(x, y);
}
}
}

37
backend/src/Squidex.Infrastructure/Reflection/Equality/DictionaryComparer{TKey,TValue}.cs

@ -0,0 +1,37 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class DictionaryComparer<TKey, TValue> : IDeepComparer where TKey : notnull
{
private readonly IEqualityComparer<KeyValuePair<TKey, TValue>> comparer;
public DictionaryComparer(IDeepComparer comparer)
{
this.comparer = new CollectionExtensions.KeyValuePairComparer<TKey, TValue>(
new DeepEqualityComparer<TKey>(comparer),
new DeepEqualityComparer<TValue>(comparer));
}
public bool IsEquals(object? x, object? y)
{
var lhs = (IReadOnlyDictionary<TKey, TValue>)x!;
var rhs = (IReadOnlyDictionary<TKey, TValue>)y!;
if (lhs.Count != rhs.Count)
{
return false;
}
return !lhs.Except(rhs, comparer).Any();
}
}
}

14
backend/src/Squidex.Infrastructure/Reflection/Equality/IDeepComparer.cs

@ -0,0 +1,14 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Reflection.Equality
{
public interface IDeepComparer
{
bool IsEquals(object? x, object? y);
}
}

17
backend/src/Squidex.Infrastructure/Reflection/Equality/NoopComparer.cs

@ -0,0 +1,17 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class NoopComparer : IDeepComparer
{
public bool IsEquals(object? x, object? y)
{
return false;
}
}
}

50
backend/src/Squidex.Infrastructure/Reflection/Equality/ObjectComparer.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Reflection;
using Squidex.Infrastructure.Reflection.Internal;
namespace Squidex.Infrastructure.Reflection.Equality
{
public sealed class ObjectComparer : IDeepComparer
{
private readonly PropertyAccessor[] propertyAccessors;
private readonly IDeepComparer valueComparer;
public ObjectComparer(IDeepComparer valueComparer, Type type)
{
propertyAccessors =
type.GetPublicProperties()
.Where(x => x.CanRead)
.Where(x => x.GetCustomAttribute<IgnoreEqualsAttribute>() == null)
.Select(x => new PropertyAccessor(x.DeclaringType!, x))
.ToArray();
this.valueComparer = valueComparer;
}
public bool IsEquals(object? x, object? y)
{
for (var i = 0; i < propertyAccessors.Length; i++)
{
var property = propertyAccessors[i];
var lhs = property.Get(x!);
var rhs = property.Get(y!);
if (!valueComparer.IsEquals(lhs, rhs))
{
return false;
}
}
return true;
}
}
}

35
backend/src/Squidex.Infrastructure/Reflection/Equality/SetComparer.cs

@ -0,0 +1,35 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
namespace Squidex.Infrastructure.Reflection.Equality
{
internal sealed class SetComparer<T> : IDeepComparer
{
private readonly IEqualityComparer<T> equalityComparer;
public SetComparer(IDeepComparer comparer)
{
equalityComparer = new DeepEqualityComparer<T>(comparer);
}
public bool IsEquals(object? x, object? y)
{
var lhs = (ISet<T>)x!;
var rhs = (ISet<T>)y!;
if (lhs.Count != rhs.Count)
{
return false;
}
return lhs.Intersect(rhs, equalityComparer).Count() == rhs.Count;
}
}
}

10
backend/src/Squidex.Infrastructure/Reflection/IPropertyAccessor.cs → backend/src/Squidex.Infrastructure/Reflection/IgnoreEqualsAttribute.cs

@ -1,16 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Infrastructure.Reflection
{
public interface IPropertyAccessor
[AttributeUsage(AttributeTargets.Property)]
public sealed class IgnoreEqualsAttribute : Attribute
{
object? Get(object target);
void Set(object target, object? value);
}
}

15
backend/src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs → backend/src/Squidex.Infrastructure/Reflection/Internal/PropertyAccessor.cs

@ -8,10 +8,17 @@
using System;
using System.Reflection;
namespace Squidex.Infrastructure.Reflection
namespace Squidex.Infrastructure.Reflection.Internal
{
public sealed class PropertyAccessor : IPropertyAccessor
public sealed class PropertyAccessor
{
private interface IPropertyAccessor
{
object? Get(object target);
void Set(object target, object? value);
}
private sealed class PropertyWrapper<TObject, TValue> : IPropertyAccessor
{
private readonly Func<TObject, TValue> getMethod;
@ -56,7 +63,9 @@ namespace Squidex.Infrastructure.Reflection
Guard.NotNull(targetType);
Guard.NotNull(propertyInfo);
internalAccessor = (IPropertyAccessor)Activator.CreateInstance(typeof(PropertyWrapper<,>).MakeGenericType(propertyInfo.DeclaringType!, propertyInfo.PropertyType), propertyInfo)!;
var type = typeof(PropertyWrapper<,>).MakeGenericType(propertyInfo.DeclaringType!, propertyInfo.PropertyType);
internalAccessor = (IPropertyAccessor)Activator.CreateInstance(type, propertyInfo)!;
}
public object? Get(object target)

0
backend/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs → backend/src/Squidex.Infrastructure/Reflection/Internal/ReflectionExtensions.cs

78
backend/src/Squidex.Infrastructure/Reflection/PropertiesTypeAccessor.cs

@ -1,78 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
namespace Squidex.Infrastructure.Reflection
{
public sealed class PropertiesTypeAccessor
{
private static readonly ConcurrentDictionary<Type, PropertiesTypeAccessor> AccessorCache = new ConcurrentDictionary<Type, PropertiesTypeAccessor>();
private readonly Dictionary<string, IPropertyAccessor> accessors = new Dictionary<string, IPropertyAccessor>();
private readonly List<PropertyInfo> properties = new List<PropertyInfo>();
public IEnumerable<PropertyInfo> Properties
{
get
{
return properties;
}
}
private PropertiesTypeAccessor(Type type)
{
var allProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var property in allProperties)
{
accessors[property.Name] = new PropertyAccessor(type, property);
properties.Add(property);
}
}
public static PropertiesTypeAccessor Create(Type targetType)
{
Guard.NotNull(targetType);
return AccessorCache.GetOrAdd(targetType, x => new PropertiesTypeAccessor(x));
}
public void SetValue(object target, string propertyName, object? value)
{
Guard.NotNull(target);
var accessor = FindAccessor(propertyName);
accessor.Set(target, value);
}
public object? GetValue(object target, string propertyName)
{
Guard.NotNull(target);
var accessor = FindAccessor(propertyName);
return accessor.Get(target);
}
private IPropertyAccessor FindAccessor(string propertyName)
{
Guard.NotNullOrEmpty(propertyName);
if (!accessors.TryGetValue(propertyName, out var accessor))
{
throw new ArgumentException("Property does not exist.", nameof(propertyName));
}
return accessor;
}
}
}

84
backend/src/Squidex.Infrastructure/Reflection/SimpleCopier.cs

@ -1,84 +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;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Infrastructure.Reflection
{
public static class SimpleCopier
{
private struct PropertyMapper
{
private readonly IPropertyAccessor accessor;
private readonly Func<object?, object?> converter;
public PropertyMapper(IPropertyAccessor accessor, Func<object?, object?> converter)
{
this.accessor = accessor;
this.converter = converter;
}
public void MapProperty(object source, object target)
{
var value = converter(accessor.Get(source));
accessor.Set(target, value);
}
}
private static class ClassCopier<T> where T : class, new()
{
private static readonly List<PropertyMapper> Mappers = new List<PropertyMapper>();
static ClassCopier()
{
var type = typeof(T);
foreach (var property in type.GetPublicProperties())
{
if (!property.CanWrite || !property.CanRead)
{
continue;
}
var accessor = new PropertyAccessor(type, property);
if (property.PropertyType.Implements<ICloneable>())
{
Mappers.Add(new PropertyMapper(accessor, x => ((ICloneable)x!)?.Clone()));
}
else
{
Mappers.Add(new PropertyMapper(accessor, x => x));
}
}
}
public static T CopyThis(T source)
{
var destination = new T();
foreach (var mapper in Mappers)
{
mapper.MapProperty(source, destination);
}
return destination;
}
}
public static T Copy<T>(this T source) where T : class, new()
{
Guard.NotNull(source);
return ClassCopier<T>.CopyThis(source);
}
}
}

121
backend/src/Squidex.Infrastructure/Reflection/SimpleEquals.cs

@ -0,0 +1,121 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure.Reflection.Equality;
using Squidex.Infrastructure.Reflection.Internal;
namespace Squidex.Infrastructure.Reflection
{
public static class SimpleEquals
{
private static readonly ConcurrentDictionary<Type, IDeepComparer> Comparers = new ConcurrentDictionary<Type, IDeepComparer>();
private static readonly DefaultComparer DefaultComparer = new DefaultComparer();
private static readonly NoopComparer NoopComparer = new NoopComparer();
internal static IDeepComparer Build(Type type)
{
return BuildCore(type) ?? DefaultComparer;
}
internal static IDeepComparer BuildInner(Type type)
{
return BuildCore(type) ?? NoopComparer;
}
private static IDeepComparer BuildCore(Type t)
{
return Comparers.GetOrAdd(t, type =>
{
if (IsSimpleType(type) || IsEquatable(type))
{
return NoopComparer;
}
if (IsArray(type))
{
var comparerType = typeof(ArrayComparer<>).MakeGenericType(type.GetElementType()!);
return (IDeepComparer)Activator.CreateInstance(comparerType, DefaultComparer)!;
}
if (IsSet(type))
{
var comparerType = typeof(SetComparer<>).MakeGenericType(type.GetGenericArguments());
return (IDeepComparer)Activator.CreateInstance(comparerType, DefaultComparer)!;
}
if (IsDictionary(type))
{
var comparerType = typeof(DictionaryComparer<,>).MakeGenericType(type.GetGenericArguments());
return (IDeepComparer)Activator.CreateInstance(comparerType, DefaultComparer)!;
}
if (IsCollection(type))
{
PropertyAccessor? count = null;
var countProperty = type.GetProperty("Count");
if (countProperty != null && countProperty.PropertyType == typeof(int))
{
count = new PropertyAccessor(type, countProperty);
}
return (IDeepComparer)Activator.CreateInstance(typeof(CollectionComparer), DefaultComparer, count)!;
}
return new ObjectComparer(DefaultComparer, type);
});
}
private static bool IsArray(Type type)
{
return type.IsArray;
}
private static bool IsCollection(Type type)
{
return type.GetInterfaces().Contains(typeof(IEnumerable));
}
private static bool IsSimpleType(Type type)
{
return type.IsValueType || type == typeof(string);
}
private static bool IsEquatable(Type type)
{
return type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEquatable<>));
}
private static bool IsSet(Type type)
{
return
type.GetGenericArguments().Length == 1 &&
type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ISet<>));
}
private static bool IsDictionary(Type type)
{
return
type.GetGenericArguments().Length == 2 &&
type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>));
}
public static bool IsEquals<T>(T x, T y)
{
return DefaultComparer.IsEquals(x, y);
}
}
}

15
backend/src/Squidex.Infrastructure/Reflection/SimpleMapper.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Squidex.Infrastructure.Reflection.Internal;
#pragma warning disable RECS0108 // Warns about static fields in generic types
@ -19,8 +20,8 @@ namespace Squidex.Infrastructure.Reflection
private sealed class StringConversionPropertyMapper : PropertyMapper
{
public StringConversionPropertyMapper(
IPropertyAccessor sourceAccessor,
IPropertyAccessor targetAccessor)
PropertyAccessor sourceAccessor,
PropertyAccessor targetAccessor)
: base(sourceAccessor, targetAccessor)
{
}
@ -38,8 +39,8 @@ namespace Squidex.Infrastructure.Reflection
private readonly Type targetType;
public ConversionPropertyMapper(
IPropertyAccessor sourceAccessor,
IPropertyAccessor targetAccessor,
PropertyAccessor sourceAccessor,
PropertyAccessor targetAccessor,
Type targetType)
: base(sourceAccessor, targetAccessor)
{
@ -70,10 +71,10 @@ namespace Squidex.Infrastructure.Reflection
private class PropertyMapper
{
private readonly IPropertyAccessor sourceAccessor;
private readonly IPropertyAccessor targetAccessor;
private readonly PropertyAccessor sourceAccessor;
private readonly PropertyAccessor targetAccessor;
public PropertyMapper(IPropertyAccessor sourceAccessor, IPropertyAccessor targetAccessor)
public PropertyMapper(PropertyAccessor sourceAccessor, PropertyAccessor targetAccessor)
{
this.sourceAccessor = sourceAccessor;
this.targetAccessor = targetAccessor;

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

@ -9,7 +9,8 @@ using System;
namespace Squidex.Infrastructure.Security
{
public sealed partial class Permission : IComparable<Permission>, IEquatable<Permission>
[Equals(DoNotAddEqualityOperators = true)]
public sealed partial class Permission : IComparable<Permission>
{
public const string Any = "*";
public const string Exclude = "^";
@ -22,6 +23,7 @@ namespace Squidex.Infrastructure.Security
get { return id; }
}
[IgnoreDuringEquals]
private Part[] Path
{
get { return path ??= Part.ParsePath(id); }

5
backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -9,7 +9,12 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Equals.Fody" Version="1.9.5" />
<PackageReference Include="FluentFTP" Version="28.0.2" />
<PackageReference Include="Fody" Version="4.2.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="McMaster.NETCore.Plugins" Version="0.3.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Version="3.1.0" />

2
backend/src/Squidex/Areas/Api/Controllers/Apps/Models/WorkflowTransitionDto.cs

@ -29,7 +29,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
public WorkflowTransition ToTransition()
{
return new WorkflowTransition(Expression, Roles);
return WorkflowTransition.When(Expression, Roles);
}
}
}

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RoleTests.cs

@ -72,7 +72,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps
{
var role = new Role("Custom");
Assert.False(role.Equals(null!));
Assert.False(role.Equals((string)null!));
Assert.False(role.Equals("Other"));
}
}

14
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Contents;
using Xunit;
@ -16,7 +18,17 @@ namespace Squidex.Domain.Apps.Core.Model.Contents
[Fact]
public void Should_serialize_and_deserialize()
{
var workflow = Workflows.Empty.Set(Workflow.Default);
var workflow = new Workflow(
Status.Draft, new Dictionary<Status, WorkflowStep>
{
[Status.Draft] = new WorkflowStep(
new Dictionary<Status, WorkflowTransition>
{
[Status.Published] = WorkflowTransition.When("Expression", "Role1", "Role2")
},
"#00ff00",
NoUpdate.When("Expression", "Role1", "Role2"))
}, new List<Guid> { Guid.NewGuid() }, "MyName");
var serialized = workflow.SerializeAndDeserialize();

2
backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas
var lhs = (FieldProperties)Activator.CreateInstance(type)!;
var rhs = (FieldProperties)Activator.CreateInstance(type)!;
Assert.True(lhs.DeepEquals(rhs));
Assert.True(lhs.Equals(rhs));
}
[Fact]

56
backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs

@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name);
@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppName, sut.Snapshot.Name);
@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal("my-label", sut.Snapshot.Label);
Assert.Equal("my-description", sut.Snapshot.Description);
@ -155,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal("image/png", sut.Snapshot.Image!.MimeType);
@ -175,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Null(sut.Snapshot.Image);
@ -216,7 +216,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(4));
result.ShouldBeEquivalent(new EntitySavedResult(4));
Assert.Equal(planIdPaid, sut.Snapshot.Plan!.PlanId);
@ -242,7 +242,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(5));
result.ShouldBeEquivalent(new EntitySavedResult(5));
Assert.Null(sut.Snapshot.Plan);
@ -293,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(new RedirectToCheckoutResult(new Uri("http://squidex.io")));
result.ShouldBeEquivalent(new RedirectToCheckoutResult(new Uri("http://squidex.io")));
Assert.Null(sut.Snapshot.Plan);
}
@ -307,7 +307,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(4));
result.ShouldBeEquivalent(new EntitySavedResult(4));
A.CallTo(() => appPlansBillingManager.ChangePlanAsync(Actor.Identifier, AppNamedId, planIdPaid))
.MustNotHaveHappened();
@ -322,7 +322,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Role.Editor, sut.Snapshot.Contributors[contributorId]);
@ -342,7 +342,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]);
@ -362,7 +362,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.Contributors.ContainsKey(contributorId));
@ -381,7 +381,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.Clients.ContainsKey(clientId));
@ -401,7 +401,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(clientNewName, sut.Snapshot.Clients[clientId].Name);
@ -422,7 +422,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.Clients.ContainsKey(clientId));
@ -441,7 +441,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.NotEmpty(sut.Snapshot.Workflows);
@ -461,7 +461,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.NotEmpty(sut.Snapshot.Workflows);
@ -481,7 +481,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Empty(sut.Snapshot.Workflows);
@ -500,7 +500,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -520,7 +520,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -540,7 +540,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE));
@ -559,7 +559,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(1, sut.Snapshot.Roles.CustomCount);
@ -579,7 +579,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(0, sut.Snapshot.Roles.CustomCount);
@ -599,7 +599,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -616,7 +616,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count);
@ -636,7 +636,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count);
@ -656,7 +656,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -673,7 +673,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(4));
result.ShouldBeEquivalent(new EntitySavedResult(4));
LastEvents
.ShouldHaveSameEvents(

18
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(0, sut.Snapshot.FileVersion);
Assert.Equal(command.FileHash, sut.Snapshot.FileHash);
@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(1, sut.Snapshot.FileVersion);
Assert.Equal(command.FileHash, sut.Snapshot.FileHash);
@ -124,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.FileName, sut.Snapshot.FileName);
@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Slug, sut.Snapshot.Slug);
@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.IsProtected, sut.Snapshot.IsProtected);
@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Metadata, sut.Snapshot.Metadata);
@ -200,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -217,7 +217,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(parentId, sut.Snapshot.ParentId);
@ -237,7 +237,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(2));
result.ShouldBeEquivalent(new EntitySavedResult(2));
Assert.True(sut.Snapshot.IsDeleted);

8
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetFolderDomainObjectTests.cs

@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.FolderName, sut.Snapshot.FolderName);
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.FolderName, sut.Snapshot.FolderName);
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(parentId, sut.Snapshot.ParentId);
@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(1));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.True(sut.Snapshot.IsDeleted);

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs

@ -170,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.AssetTake(query.AdjustToModel());
cursor.Take(query.AdjustToModel());
A.CallTo(() => cursor.Limit(3))
.MustHaveHappened();
@ -182,7 +182,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.AssetSkip(query.AdjustToModel());
cursor.Skip(query.AdjustToModel());
A.CallTo(() => cursor.Skip(3))
.MustHaveHappened();
@ -210,7 +210,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString();
});
cursor.AssetSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel());
cursor.Sort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel());
return i;
}

6
backend/tests/Squidex.Domain.Apps.Entities.Tests/Comments/CommentsGrainTests.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
var result = await sut.ExecuteAsync(CreateCommentsCommand(command));
result.ShouldBeEquivalent(EntityCreatedResult.Create(command.CommentId, 0));
result.ShouldBeEquivalent((object)EntityCreatedResult.Create(command.CommentId, 0));
sut.GetCommentsAsync(0).Result.Should().BeEquivalentTo(new CommentsResult { Version = 0 });
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult
@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
var result = await sut.ExecuteAsync(CreateCommentsCommand(updateCommand));
result.ShouldBeEquivalent(new EntitySavedResult(1));
result.ShouldBeEquivalent((object)new EntitySavedResult(1));
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult
{
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Comments
var result = await sut.ExecuteAsync(CreateCommentsCommand(deleteCommand));
result.ShouldBeEquivalent(new EntitySavedResult(2));
result.ShouldBeEquivalent((object)new EntitySavedResult(2));
sut.GetCommentsAsync(-1).Result.Should().BeEquivalentTo(new CommentsResult { Version = 2 });
sut.GetCommentsAsync(0).Result.Should().BeEquivalentTo(new CommentsResult

34
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs

@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Draft, sut.Snapshot.Status);
@ -148,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Published, sut.Snapshot.Status);
@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -202,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.IsPending);
@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Single(LastEvents);
@ -251,7 +251,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -272,7 +272,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.IsPending);
@ -294,7 +294,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Single(LastEvents);
@ -311,7 +311,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Published, sut.Snapshot.Status);
@ -333,7 +333,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Archived, sut.Snapshot.Status);
@ -356,7 +356,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Draft, sut.Snapshot.Status);
@ -379,7 +379,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Draft, sut.Snapshot.Status);
@ -403,7 +403,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.IsPending);
@ -427,7 +427,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(Status.Draft, sut.Snapshot.Status);
Assert.Equal(Status.Published, sut.Snapshot.ScheduleJob!.Status);
@ -455,7 +455,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Null(sut.Snapshot.ScheduleJob);
@ -477,7 +477,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(new EntitySavedResult(1));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.True(sut.Snapshot.IsDeleted);
@ -501,7 +501,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var result = await PublishAsync(CreateContentCommand(command));
result.ShouldBeEquivalent2(new EntitySavedResult(3));
result.ShouldBeEquivalent(new EntitySavedResult(3));
Assert.False(sut.Snapshot.IsPending);

9
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs

@ -16,8 +16,9 @@ using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors;
using Squidex.Domain.Apps.Entities.MongoDb.Contents;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
@ -276,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.ContentTake(query.AdjustToModel(schemaDef, false));
cursor.Take(query);
A.CallTo(() => cursor.Limit(3))
.MustHaveHappened();
@ -288,7 +289,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.ContentSkip(query.AdjustToModel(schemaDef, false));
cursor.Skip(query);
A.CallTo(() => cursor.Skip(3))
.MustHaveHappened();
@ -316,7 +317,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString();
});
cursor.ContentSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false));
cursor.Sort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false));
return i;
}

10
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs

@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppId, sut.Snapshot.AppId.Id);
@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(1));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Same(command.Trigger, sut.Snapshot.RuleDef.Trigger);
Assert.Same(command.Action, sut.Snapshot.RuleDef.Action);
@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.RuleDef.IsEnabled);
@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.RuleDef.IsEnabled);
@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(1));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.True(sut.Snapshot.IsDeleted);

60
backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs

@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(AppId, sut.Snapshot.AppId.Id);
@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
var @event = (SchemaCreated)LastEvents.Single().Payload;
@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Properties, sut.Snapshot.SchemaDef.Properties);
@ -146,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -167,7 +167,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.FieldsInLists, sut.Snapshot.SchemaDef.FieldsInLists);
@ -190,7 +190,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.FieldsInReferences, sut.Snapshot.SchemaDef.FieldsInReferences);
@ -209,7 +209,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(sut.Snapshot.SchemaDef.IsPublished);
@ -229,7 +229,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(sut.Snapshot.SchemaDef.IsPublished);
@ -248,7 +248,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Name, sut.Snapshot.SchemaDef.Category);
@ -273,7 +273,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls);
@ -292,7 +292,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(new EntitySavedResult(1));
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.True(sut.Snapshot.IsDeleted);
@ -313,7 +313,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -333,7 +333,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
LastEvents
.ShouldHaveSameEvents(
@ -350,7 +350,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Properties, GetField(1).RawProperties);
@ -370,7 +370,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Same(command.Properties, GetNestedField(1, 2).RawProperties);
@ -390,7 +390,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Properties, GetField(1).RawProperties);
@ -411,7 +411,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Same(command.Properties, GetNestedField(1, 2).RawProperties);
@ -431,7 +431,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(GetField(1).IsDisabled);
@ -452,7 +452,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(GetNestedField(1, 2).IsLocked);
@ -472,7 +472,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(GetField(1).IsHidden);
@ -493,7 +493,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(GetNestedField(1, 2).IsHidden);
@ -514,7 +514,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(GetField(1).IsHidden);
@ -536,7 +536,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(GetNestedField(1, 2).IsHidden);
@ -556,7 +556,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(GetField(1).IsDisabled);
@ -577,7 +577,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.True(GetNestedField(1, 2).IsDisabled);
@ -598,7 +598,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(GetField(1).IsDisabled);
@ -620,7 +620,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.False(GetNestedField(1, 2).IsDisabled);
@ -640,7 +640,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Null(GetField(1));
@ -661,7 +661,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Null(GetNestedField(1, 2));
@ -683,7 +683,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var result = await PublishIdempotentAsync(command);
result.ShouldBeEquivalent2(sut.Snapshot);
result.ShouldBeEquivalent(sut.Snapshot);
Assert.Equal(command.Category, sut.Snapshot.SchemaDef.Category);

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

Loading…
Cancel
Save