diff --git a/Dockerfile.build b/Dockerfile.build
index 680882fd9..d9057ded8 100644
--- a/Dockerfile.build
+++ b/Dockerfile.build
@@ -1,4 +1,4 @@
-FROM microsoft/aspnetcore-build
+FROM microsoft/aspnetcore-build:1.1.1
# Install runtime dependencies
diff --git a/Squidex.sln b/Squidex.sln
index d75d2e1af..7c456fe9f 100644
--- a/Squidex.sln
+++ b/Squidex.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26020.0
+VisualStudioVersion = 15.0.26228.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}"
EndProject
@@ -152,16 +152,16 @@ Global
{D7166C56-178A-4457-B56A-C615C7450DEE}.Release|x86.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.ActiveCfg = Debug|x64
- {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.Build.0 = Debug|x64
- {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.ActiveCfg = Debug|x86
- {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.Build.0 = Debug|x86
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x64.Build.0 = Debug|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Debug|x86.Build.0 = Debug|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|Any CPU.ActiveCfg = Release|Any CPU
{927E1F1C-95F0-4991-B33F-603977204B02}.Release|Any CPU.Build.0 = Release|Any CPU
- {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.ActiveCfg = Release|x64
- {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.Build.0 = Release|x64
- {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.ActiveCfg = Release|x86
- {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.Build.0 = Release|x86
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.ActiveCfg = Release|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x64.Build.0 = Release|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.ActiveCfg = Release|Any CPU
+ {927E1F1C-95F0-4991-B33F-603977204B02}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Squidex.sln.DotSettings b/Squidex.sln.DotSettings
index 0adccb11b..010c5837d 100644
--- a/Squidex.sln.DotSettings
+++ b/Squidex.sln.DotSettings
@@ -14,41 +14,27 @@
True
False
True
-
- True
-
-
-
- ExplicitlyExcluded
- DO_NOT_SHOW
+
+ True
DO_NOT_SHOW
-
- DO_NOT_SHOW
- DO_NOT_SHOW
DO_NOT_SHOW
DO_NOT_SHOW
DO_NOT_SHOW
-
-
- DO_NOT_SHOW
- DO_NOT_SHOW
DO_NOT_SHOW
- DO_NOT_SHOW
DO_NOT_SHOW
- SUGGESTION
- SUGGESTION
- SUGGESTION
- DO_NOT_SHOW
DO_NOT_SHOW
DO_NOT_SHOW
DO_NOT_SHOW
DO_NOT_SHOW
+ False
+ True
+
+ ExplicitlyExcluded
TypeScript16
<?xml version="1.0" encoding="utf-16"?><Profile name="Header"><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile>
<?xml version="1.0" encoding="utf-16"?><Profile name="Namespaces"><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile>
<?xml version="1.0" encoding="utf-16"?><Profile name="Typescript"><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs></Profile>
- False
SingleQuoted
==========================================================================
$FILENAME$
@@ -99,9 +85,9 @@
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
- True
True
True
True
True
- True
\ No newline at end of file
+ True
+
\ No newline at end of file
diff --git a/media/logo-white.png b/media/logo-white.png
new file mode 100644
index 000000000..35960bba6
Binary files /dev/null and b/media/logo-white.png differ
diff --git a/media/logo-white.svg b/media/logo-white.svg
new file mode 100644
index 000000000..8ed41e69a
--- /dev/null
+++ b/media/logo-white.svg
@@ -0,0 +1,112 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/Squidex.Core/ContentEnricher.cs b/src/Squidex.Core/ContentEnricher.cs
new file mode 100644
index 000000000..387b13c32
--- /dev/null
+++ b/src/Squidex.Core/ContentEnricher.cs
@@ -0,0 +1,79 @@
+// ==========================================================================
+// ContentEnricher.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using Newtonsoft.Json.Linq;
+using Squidex.Core.Contents;
+using Squidex.Core.Schemas;
+using Squidex.Infrastructure;
+using Squidex.Infrastructure.Json;
+using System.Collections.Generic;
+
+namespace Squidex.Core
+{
+ public sealed class ContentEnricher
+ {
+ private readonly Schema schema;
+ private readonly HashSet languages;
+
+ public ContentEnricher(HashSet languages, Schema schema)
+ {
+ Guard.NotNull(schema, nameof(schema));
+ Guard.NotNull(languages, nameof(languages));
+
+ this.schema = schema;
+
+ this.languages = languages;
+ }
+
+ public void Enrich(ContentData data)
+ {
+ Guard.NotNull(data, nameof(data));
+ Guard.NotEmpty(languages, nameof(languages));
+
+ foreach (var field in schema.FieldsByName.Values)
+ {
+ var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
+
+ if (field.RawProperties.IsLocalizable)
+ {
+ foreach (var language in languages)
+ {
+ Enrich(field, fieldData, language);
+ }
+ }
+ else
+ {
+ Enrich(field, fieldData, Language.Invariant);
+ }
+
+ if (fieldData.Count > 0)
+ {
+ data.AddField(field.Name, fieldData);
+ }
+ }
+ }
+
+ private static void Enrich(Field field, ContentFieldData fieldData, Language language)
+ {
+ Guard.NotNull(fieldData, nameof(fieldData));
+ Guard.NotNull(language, nameof(language));
+
+ var defaultValue = field.RawProperties.GetDefaultValue();
+
+ if (field.RawProperties.IsRequired || defaultValue.IsNull())
+ {
+ return;
+ }
+
+ if (!fieldData.TryGetValue(language.Iso2Code, out JToken value) || value == null || value.Type == JTokenType.Null)
+ {
+ fieldData.AddValue(language.Iso2Code, defaultValue);
+ }
+ }
+ }
+}
diff --git a/src/Squidex.Core/ContentExtensions.cs b/src/Squidex.Core/ContentExtensions.cs
new file mode 100644
index 000000000..3decba2bf
--- /dev/null
+++ b/src/Squidex.Core/ContentExtensions.cs
@@ -0,0 +1,52 @@
+// ==========================================================================
+// ContentExtensions.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Squidex.Core.Contents;
+using Squidex.Core.Schemas;
+using Squidex.Infrastructure;
+
+namespace Squidex.Core
+{
+ public static class ContentExtensions
+ {
+ public static ContentData Enrich(this ContentData data, Schema schema, HashSet languages)
+ {
+ var validator = new ContentEnricher(languages, schema);
+
+ validator.Enrich(data);
+
+ return data;
+ }
+
+ public static async Task ValidateAsync(this ContentData data, Schema schema, HashSet languages, IList errors)
+ {
+ var validator = new ContentValidator(schema, languages);
+
+ await validator.ValidateAsync(data);
+
+ foreach (var error in validator.Errors)
+ {
+ errors.Add(error);
+ }
+ }
+
+ public static async Task ValidatePartialAsync(this ContentData data, Schema schema, HashSet languages, IList errors)
+ {
+ var validator = new ContentValidator(schema, languages);
+
+ await validator.ValidatePartialAsync(data);
+
+ foreach (var error in validator.Errors)
+ {
+ errors.Add(error);
+ }
+ }
+ }
+}
diff --git a/src/Squidex.Core/ContentValidator.cs b/src/Squidex.Core/ContentValidator.cs
new file mode 100644
index 000000000..39fc32f2a
--- /dev/null
+++ b/src/Squidex.Core/ContentValidator.cs
@@ -0,0 +1,190 @@
+// ==========================================================================
+// ContentValidator.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+using Squidex.Core.Contents;
+using Squidex.Core.Schemas;
+using Squidex.Infrastructure;
+
+namespace Squidex.Core
+{
+ public sealed class ContentValidator
+ {
+ private readonly Schema schema;
+ private readonly HashSet languages;
+ private readonly List errors = new List();
+
+ public ContentValidator(Schema schema, HashSet languages)
+ {
+ Guard.NotNull(schema, nameof(schema));
+ Guard.NotNull(languages, nameof(languages));
+
+ this.schema = schema;
+
+ this.languages = languages;
+ }
+
+ public IReadOnlyList Errors
+ {
+ get { return errors; }
+ }
+
+ public async Task ValidatePartialAsync(ContentData data)
+ {
+ Guard.NotNull(data, nameof(data));
+
+ foreach (var fieldData in data)
+ {
+ var fieldName = fieldData.Key;
+
+ if (!schema.FieldsByName.TryGetValue(fieldData.Key, out Field field))
+ {
+ AddError(" is not a known field", fieldName);
+ }
+ else
+ {
+ if (field.RawProperties.IsLocalizable)
+ {
+ await ValidateLocalizableFieldPartialAsync(field, fieldData.Value);
+ }
+ else
+ {
+ await ValidateNonLocalizableFieldPartialAsync(field, fieldData.Value);
+ }
+ }
+ }
+ }
+
+ private async Task ValidateLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
+ {
+ foreach (var languageValue in fieldData)
+ {
+ if (!Language.TryGetLanguage(languageValue.Key, out Language language))
+ {
+ AddError($" has an invalid language '{languageValue.Key}'", field);
+ }
+ else if (!languages.Contains(language))
+ {
+ AddError($" has an unsupported language '{languageValue.Key}'", field);
+ }
+ else
+ {
+ await ValidateAsync(field, languageValue.Value, language);
+ }
+ }
+ }
+
+ private async Task ValidateNonLocalizableFieldPartialAsync(Field field, ContentFieldData fieldData)
+ {
+ if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
+ {
+ AddError($" can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
+ }
+
+ if (fieldData.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
+ {
+ await ValidateAsync(field, value);
+ }
+ }
+
+ public async Task ValidateAsync(ContentData data)
+ {
+ Guard.NotNull(data, nameof(data));
+
+ ValidateUnknownFields(data);
+
+ foreach (var field in schema.FieldsByName.Values)
+ {
+ var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
+
+ if (field.RawProperties.IsLocalizable)
+ {
+ await ValidateLocalizableFieldAsync(field, fieldData);
+ }
+ else
+ {
+ await ValidateNonLocalizableField(field, fieldData);
+ }
+ }
+ }
+
+ private void ValidateUnknownFields(ContentData data)
+ {
+ foreach (var fieldData in data)
+ {
+ if (!schema.FieldsByName.ContainsKey(fieldData.Key))
+ {
+ AddError(" is not a known field", fieldData.Key);
+ }
+ }
+ }
+
+ private async Task ValidateLocalizableFieldAsync(Field field, ContentFieldData fieldData)
+ {
+ foreach (var valueLanguage in fieldData.Keys)
+ {
+ if (!Language.TryGetLanguage(valueLanguage, out Language language))
+ {
+ AddError($" has an invalid language '{valueLanguage}'", field);
+ }
+ else if (!languages.Contains(language))
+ {
+ AddError($" has an unsupported language '{valueLanguage}'", field);
+ }
+ }
+
+ foreach (var language in languages)
+ {
+ var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
+
+ await ValidateAsync(field, value, language);
+ }
+ }
+
+ private async Task ValidateNonLocalizableField(Field field, ContentFieldData fieldData)
+ {
+ if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
+ {
+ AddError($" can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})", field);
+ }
+
+ var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
+
+ await ValidateAsync(field, value);
+ }
+
+ private Task ValidateAsync(Field field, JToken value, Language language = null)
+ {
+ return field.ValidateAsync(value, m => AddError(m, field, language));
+ }
+
+ private void AddError(string message, Field field, Language language = null)
+ {
+ var displayName = !string.IsNullOrWhiteSpace(field.RawProperties.Label) ? field.RawProperties.Label : field.Name;
+
+ if (language != null)
+ {
+ displayName += $" ({language.Iso2Code})";
+ }
+
+ message = message.Replace("", displayName);
+
+ errors.Add(new ValidationError(message, field.Name));
+ }
+
+ private void AddError(string message, string fieldName)
+ {
+ message = message.Replace("", fieldName);
+
+ errors.Add(new ValidationError(message, fieldName));
+ }
+ }
+}
diff --git a/src/Squidex.Core/Contents/ContentData.cs b/src/Squidex.Core/Contents/ContentData.cs
index 2766b6251..283dd52a4 100644
--- a/src/Squidex.Core/Contents/ContentData.cs
+++ b/src/Squidex.Core/Contents/ContentData.cs
@@ -62,6 +62,28 @@ namespace Squidex.Core.Contents
return result;
}
+ public ContentData ToCleaned()
+ {
+ var result = new ContentData();
+
+ foreach (var fieldValue in this.Where(x => x.Value != null))
+ {
+ var resultValue = new ContentFieldData();
+
+ foreach (var languageValue in fieldValue.Value.Where(x => x.Value != null && x.Value.Type != JTokenType.Null))
+ {
+ resultValue[languageValue.Key] = languageValue.Value;
+ }
+
+ if (resultValue.Count > 0)
+ {
+ result[fieldValue.Key] = resultValue;
+ }
+ }
+
+ return result;
+ }
+
public ContentData ToIdModel(Schema schema)
{
Guard.NotNull(schema, nameof(schema));
@@ -130,6 +152,10 @@ namespace Squidex.Core.Contents
{
fieldResult.Add(languageCode, value);
}
+ else if (language.Equals(masterLanguage) && fieldValues.TryGetValue(invariantCode, out value))
+ {
+ fieldResult.Add(languageCode, value);
+ }
}
}
else
diff --git a/src/Squidex.Core/FieldExtensions.cs b/src/Squidex.Core/FieldExtensions.cs
new file mode 100644
index 000000000..1128a2190
--- /dev/null
+++ b/src/Squidex.Core/FieldExtensions.cs
@@ -0,0 +1,39 @@
+// ==========================================================================
+// FieldExtensions.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+using Squidex.Core.Schemas;
+using Squidex.Infrastructure;
+using Squidex.Infrastructure.Json;
+
+namespace Squidex.Core
+{
+ public static class FieldExtensions
+ {
+ public static async Task ValidateAsync(this Field field, JToken value, Action addError)
+ {
+ Guard.NotNull(value, nameof(value));
+
+ try
+ {
+ var typedValue = value.IsNull() ? null : field.ConvertValue(value);
+
+ foreach (var validator in field.Validators)
+ {
+ await validator.ValidateAsync(typedValue, addError);
+ }
+ }
+ catch
+ {
+ addError(" is not a valid value");
+ }
+ }
+ }
+}
diff --git a/src/Squidex.Core/Identity/SquidexClaimTypes.cs b/src/Squidex.Core/Identity/SquidexClaimTypes.cs
index bbdb0877a..c5b77417e 100644
--- a/src/Squidex.Core/Identity/SquidexClaimTypes.cs
+++ b/src/Squidex.Core/Identity/SquidexClaimTypes.cs
@@ -8,7 +8,7 @@
namespace Squidex.Core.Identity
{
- public class SquidexClaimTypes
+ public static class SquidexClaimTypes
{
public static readonly string SquidexDisplayName = "urn:squidex:name";
diff --git a/src/Squidex.Core/Schemas/BooleanField.cs b/src/Squidex.Core/Schemas/BooleanField.cs
index 5944b4a72..428be63ce 100644
--- a/src/Squidex.Core/Schemas/BooleanField.cs
+++ b/src/Squidex.Core/Schemas/BooleanField.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
@@ -30,12 +31,12 @@ namespace Squidex.Core.Schemas
}
}
- protected override object ConvertValue(JToken value)
+ public override object ConvertValue(JToken value)
{
return (bool?)value;
}
- protected override void PrepareJsonSchema(JsonProperty jsonProperty)
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
{
jsonProperty.Type = JsonObjectType.Boolean;
}
diff --git a/src/Squidex.Core/Schemas/BooleanFieldEditor.cs b/src/Squidex.Core/Schemas/BooleanFieldEditor.cs
index 15105e5c7..fd6d7fe5f 100644
--- a/src/Squidex.Core/Schemas/BooleanFieldEditor.cs
+++ b/src/Squidex.Core/Schemas/BooleanFieldEditor.cs
@@ -10,6 +10,7 @@ namespace Squidex.Core.Schemas
{
public enum BooleanFieldEditor
{
- Checkbox
+ Checkbox,
+ Toggle
}
}
diff --git a/src/Squidex.Core/Schemas/Cloneable.cs b/src/Squidex.Core/Schemas/CloneableBase.cs
similarity index 89%
rename from src/Squidex.Core/Schemas/Cloneable.cs
rename to src/Squidex.Core/Schemas/CloneableBase.cs
index 98908d4c6..6b1eecefe 100644
--- a/src/Squidex.Core/Schemas/Cloneable.cs
+++ b/src/Squidex.Core/Schemas/CloneableBase.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// Cloneable.cs
+// CloneableBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@@ -10,9 +10,9 @@ using System;
namespace Squidex.Core.Schemas
{
- public abstract class Cloneable
+ public abstract class CloneableBase
{
- protected T Clone(Action updater) where T : Cloneable
+ protected T Clone(Action updater) where T : CloneableBase
{
var clone = (T)MemberwiseClone();
diff --git a/src/Squidex.Core/Schemas/DateTimeField.cs b/src/Squidex.Core/Schemas/DateTimeField.cs
index 7670cfd66..af3ed662d 100644
--- a/src/Squidex.Core/Schemas/DateTimeField.cs
+++ b/src/Squidex.Core/Schemas/DateTimeField.cs
@@ -17,6 +17,7 @@ using NodaTime.Text;
using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
+// ReSharper disable InvertIf
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable ConvertIfStatementToSwitchStatement
@@ -43,7 +44,7 @@ namespace Squidex.Core.Schemas
}
}
- protected override object ConvertValue(JToken value)
+ public override object ConvertValue(JToken value)
{
if (value.Type == JTokenType.String)
{
@@ -57,15 +58,10 @@ namespace Squidex.Core.Schemas
return parseResult.Value;
}
- if (value.Type == JTokenType.Null)
- {
- return null;
- }
-
throw new InvalidCastException("Invalid json type, expected string.");
}
- protected override void PrepareJsonSchema(JsonProperty jsonProperty)
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
{
jsonProperty.Type = JsonObjectType.String;
diff --git a/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs b/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs
index eefcca87d..189911847 100644
--- a/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs
+++ b/src/Squidex.Core/Schemas/DateTimeFieldProperties.cs
@@ -67,7 +67,7 @@ namespace Squidex.Core.Schemas
public override JToken GetDefaultValue()
{
- return DefaultValue != null ? DefaultValue.ToString() : null;
+ return DefaultValue?.ToString();
}
protected override IEnumerable ValidateCore()
diff --git a/src/Squidex.Core/Schemas/Field.cs b/src/Squidex.Core/Schemas/Field.cs
index 5f5636b94..60d6d2b2a 100644
--- a/src/Squidex.Core/Schemas/Field.cs
+++ b/src/Squidex.Core/Schemas/Field.cs
@@ -8,12 +8,11 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Newtonsoft.Json.Linq;
using NJsonSchema;
-using Squidex.Core.Contents;
+using Squidex.Core.Schemas.Validators;
using Squidex.Infrastructure;
// ReSharper disable InvertIf
@@ -22,7 +21,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
- public abstract class Field : Cloneable
+ public abstract class Field : CloneableBase
{
private readonly Lazy> validators;
private readonly long id;
@@ -50,6 +49,11 @@ namespace Squidex.Core.Schemas
get { return isDisabled; }
}
+ public IReadOnlyList Validators
+ {
+ get { return validators.Value; }
+ }
+
public abstract FieldProperties RawProperties { get; }
protected Field(long id, string name)
@@ -66,53 +70,7 @@ namespace Squidex.Core.Schemas
public abstract Field Update(FieldProperties newProperties);
- public void Enrich(ContentFieldData fieldData, Language language)
- {
- Guard.NotNull(fieldData, nameof(fieldData));
- Guard.NotNull(language, nameof(language));
-
- var defaultValue = RawProperties.GetDefaultValue();
-
- if (!RawProperties.IsRequired && defaultValue != null && fieldData.GetOrDefault(language.Iso2Code) == null)
- {
- fieldData.AddValue(language.Iso2Code, defaultValue);
- }
- }
-
- public async Task ValidateAsync(JToken value, ICollection errors, Language language = null)
- {
- Guard.NotNull(value, nameof(value));
-
- var rawErrors = new List();
- try
- {
- var typedValue = value.Type == JTokenType.Null ? null : ConvertValue(value);
-
- foreach (var validator in validators.Value)
- {
- await validator.ValidateAsync(typedValue, rawErrors);
- }
- }
- catch
- {
- rawErrors.Add(" is not a valid value");
- }
-
- if (rawErrors.Count > 0)
- {
- var displayName = !string.IsNullOrWhiteSpace(RawProperties.Label) ? RawProperties.Label : name;
-
- if (language != null)
- {
- displayName += $" ({language.Iso2Code})";
- }
-
- foreach (var error in rawErrors)
- {
- errors.Add(error.Replace("", displayName));
- }
- }
- }
+ public abstract object ConvertValue(JToken value);
public Field Hide()
{
@@ -174,7 +132,7 @@ namespace Squidex.Core.Schemas
edmType.AddStructuralProperty(Name, new EdmComplexTypeReference(languageType, false));
}
- public void AddToSchema(JsonSchema4 schema, IEnumerable languages, string schemaName, Func schemaResolver)
+ public void AddToJsonSchema(JsonSchema4 schema, IEnumerable languages, string schemaName, Func schemaResolver)
{
Guard.NotNull(schema, nameof(schema));
Guard.NotNull(languages, nameof(languages));
@@ -190,14 +148,14 @@ namespace Squidex.Core.Schemas
foreach (var language in languages)
{
- var languageProperty = new JsonProperty { Description = language.EnglishName };
+ var languageProperty = new JsonProperty { Description = language.EnglishName, IsRequired = RawProperties.IsRequired };
- PrepareJsonSchema(languageProperty);
+ PrepareJsonSchema(languageProperty, schemaResolver);
languagesObject.Properties.Add(language.Iso2Code, languageProperty);
}
- languagesProperty.AllOf.Add(schemaResolver($"{schemaName}{Name.ToPascalCase()}Property", languagesObject));
+ languagesProperty.SchemaReference = schemaResolver($"{schemaName}{Name.ToPascalCase()}Property", languagesObject);
schema.Properties.Add(Name, languagesProperty);
}
@@ -206,9 +164,9 @@ namespace Squidex.Core.Schemas
{
var jsonProperty = new JsonProperty { IsRequired = RawProperties.IsRequired, Type = JsonObjectType.Object };
- if (!string.IsNullOrWhiteSpace(RawProperties.Label))
+ if (!string.IsNullOrWhiteSpace(RawProperties.Hints))
{
- jsonProperty.Description = RawProperties.Label;
+ jsonProperty.Description = RawProperties.Hints;
}
else
{
@@ -227,8 +185,6 @@ namespace Squidex.Core.Schemas
protected abstract IEdmTypeReference CreateEdmType();
- protected abstract void PrepareJsonSchema(JsonProperty jsonProperty);
-
- protected abstract object ConvertValue(JToken value);
+ protected abstract void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver);
}
}
\ No newline at end of file
diff --git a/src/Squidex.Core/Schemas/FieldProperties.cs b/src/Squidex.Core/Schemas/FieldProperties.cs
index 0fbbea7f1..42413b2be 100644
--- a/src/Squidex.Core/Schemas/FieldProperties.cs
+++ b/src/Squidex.Core/Schemas/FieldProperties.cs
@@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
- public abstract class FieldProperties : NamedElementProperties, IValidatable
+ public abstract class FieldProperties : NamedElementPropertiesBase, IValidatable
{
private bool isRequired;
private bool isLocalizable;
diff --git a/src/Squidex.Core/Schemas/FieldRegistry.cs b/src/Squidex.Core/Schemas/FieldRegistry.cs
index 51613499a..6b4d00d60 100644
--- a/src/Squidex.Core/Schemas/FieldRegistry.cs
+++ b/src/Squidex.Core/Schemas/FieldRegistry.cs
@@ -70,9 +70,12 @@ namespace Squidex.Core.Schemas
Add(
(id, name, p) => new JsonField(id, name, (JsonFieldProperties)p));
+
+ Add(
+ (id, name, p) => new GeolocationField(id, name, (GeolocationFieldProperties)p));
}
- public void Add(FactoryFunction fieldFactory)
+ private void Add(FactoryFunction fieldFactory)
{
Guard.NotNull(fieldFactory, nameof(fieldFactory));
diff --git a/src/Squidex.Core/Schemas/GeolocationField.cs b/src/Squidex.Core/Schemas/GeolocationField.cs
new file mode 100644
index 000000000..4e79d8df6
--- /dev/null
+++ b/src/Squidex.Core/Schemas/GeolocationField.cs
@@ -0,0 +1,89 @@
+// ==========================================================================
+// GeolocationField.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.Collections.Generic;
+using Microsoft.OData.Edm;
+using Newtonsoft.Json.Linq;
+using NJsonSchema;
+using Squidex.Core.Schemas.Validators;
+using Squidex.Infrastructure;
+
+namespace Squidex.Core.Schemas
+{
+ public sealed class GeolocationField : Field
+ {
+ public GeolocationField(long id, string name, GeolocationFieldProperties properties)
+ : base(id, name, properties)
+ {
+ }
+
+ protected override IEnumerable CreateValidators()
+ {
+ if (Properties.IsRequired)
+ {
+ yield return new RequiredValidator();
+ }
+ }
+
+ public override object ConvertValue(JToken value)
+ {
+ var geolocation = (JObject)value;
+
+ foreach (var property in geolocation.Properties())
+ {
+ if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
+ {
+ throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
+ }
+ }
+
+ var lat = (double)geolocation["latitude"];
+ var lon = (double)geolocation["longitude"];
+
+ Guard.Between(lat, -90, 90, "latitude");
+ Guard.Between(lon, -180, 180, "longitude");
+
+ return value;
+ }
+
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
+ {
+ jsonProperty.Type = JsonObjectType.Object;
+
+ var geolocationSchema = new JsonSchema4();
+
+ geolocationSchema.Properties.Add("latitude", new JsonProperty
+ {
+ Type = JsonObjectType.Number,
+ Minimum = -90,
+ Maximum = 90,
+ IsRequired = true
+ });
+ geolocationSchema.Properties.Add("longitude", new JsonProperty
+ {
+ Type = JsonObjectType.Number,
+ Minimum = -180,
+ Maximum = 180,
+ IsRequired = true
+ });
+
+ geolocationSchema.AllowAdditionalProperties = false;
+
+ var schemaReference = schemaResolver("GeolocationDto", geolocationSchema);
+
+ jsonProperty.SchemaReference = schemaReference;
+ }
+
+ protected override IEdmTypeReference CreateEdmType()
+ {
+ return null;
+ }
+ }
+}
diff --git a/src/Squidex.Core/Schemas/GeolocationFieldEditor.cs b/src/Squidex.Core/Schemas/GeolocationFieldEditor.cs
new file mode 100644
index 000000000..dd2a347a6
--- /dev/null
+++ b/src/Squidex.Core/Schemas/GeolocationFieldEditor.cs
@@ -0,0 +1,15 @@
+// ==========================================================================
+// GeolocationFieldEditor.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+namespace Squidex.Core.Schemas
+{
+ public enum GeolocationFieldEditor
+ {
+ Map
+ }
+}
diff --git a/src/Squidex.Core/Schemas/GeolocationFieldProperties.cs b/src/Squidex.Core/Schemas/GeolocationFieldProperties.cs
new file mode 100644
index 000000000..ffb4cbca4
--- /dev/null
+++ b/src/Squidex.Core/Schemas/GeolocationFieldProperties.cs
@@ -0,0 +1,44 @@
+// ==========================================================================
+// GeolocationFieldProperties.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System.Collections.Generic;
+using Newtonsoft.Json.Linq;
+using Squidex.Infrastructure;
+
+namespace Squidex.Core.Schemas
+{
+ [TypeName("GeolocationField")]
+ public sealed class GeolocationFieldProperties : FieldProperties
+ {
+ private GeolocationFieldEditor editor;
+
+ public GeolocationFieldEditor Editor
+ {
+ get { return editor; }
+ set
+ {
+ ThrowIfFrozen();
+
+ editor = value;
+ }
+ }
+
+ public override JToken GetDefaultValue()
+ {
+ return null;
+ }
+
+ protected override IEnumerable ValidateCore()
+ {
+ if (!Editor.IsEnumValue())
+ {
+ yield return new ValidationError("Editor ist not a valid value", nameof(Editor));
+ }
+ }
+ }
+}
diff --git a/src/Squidex.Core/Schemas/JsonField.cs b/src/Squidex.Core/Schemas/JsonField.cs
index f12703c9b..ad9dbab35 100644
--- a/src/Squidex.Core/Schemas/JsonField.cs
+++ b/src/Squidex.Core/Schemas/JsonField.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using System.Collections.Generic;
using Microsoft.OData.Edm;
using Newtonsoft.Json.Linq;
@@ -29,12 +30,12 @@ namespace Squidex.Core.Schemas
}
}
- protected override object ConvertValue(JToken value)
+ public override object ConvertValue(JToken value)
{
return value;
}
- protected override void PrepareJsonSchema(JsonProperty jsonProperty)
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
{
jsonProperty.Type = JsonObjectType.Object;
}
diff --git a/src/Squidex.Core/Schemas/NamedElementProperties.cs b/src/Squidex.Core/Schemas/NamedElementPropertiesBase.cs
similarity index 92%
rename from src/Squidex.Core/Schemas/NamedElementProperties.cs
rename to src/Squidex.Core/Schemas/NamedElementPropertiesBase.cs
index 11a9b52aa..ac5d9cf2e 100644
--- a/src/Squidex.Core/Schemas/NamedElementProperties.cs
+++ b/src/Squidex.Core/Schemas/NamedElementPropertiesBase.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// NamedElementProperties.cs
+// NamedElementPropertiesBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@@ -10,7 +10,7 @@ using System;
namespace Squidex.Core.Schemas
{
- public abstract class NamedElementProperties
+ public abstract class NamedElementPropertiesBase
{
private string label;
private string hints;
diff --git a/src/Squidex.Core/Schemas/NumberField.cs b/src/Squidex.Core/Schemas/NumberField.cs
index aeae44b63..dd2579664 100644
--- a/src/Squidex.Core/Schemas/NumberField.cs
+++ b/src/Squidex.Core/Schemas/NumberField.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm;
@@ -41,7 +42,7 @@ namespace Squidex.Core.Schemas
}
}
- protected override void PrepareJsonSchema(JsonProperty jsonProperty)
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
{
jsonProperty.Type = JsonObjectType.Number;
@@ -61,7 +62,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.Double, !Properties.IsRequired);
}
- protected override object ConvertValue(JToken value)
+ public override object ConvertValue(JToken value)
{
return (double?)value;
}
diff --git a/src/Squidex.Core/Schemas/NumberFieldEditor.cs b/src/Squidex.Core/Schemas/NumberFieldEditor.cs
index 8e54de235..5ab5deed9 100644
--- a/src/Squidex.Core/Schemas/NumberFieldEditor.cs
+++ b/src/Squidex.Core/Schemas/NumberFieldEditor.cs
@@ -12,6 +12,7 @@ namespace Squidex.Core.Schemas
{
Input,
Radio,
- Dropdown
+ Dropdown,
+ Stars
}
}
diff --git a/src/Squidex.Core/Schemas/Schema.cs b/src/Squidex.Core/Schemas/Schema.cs
index dba1f78b9..9a3ac2b26 100644
--- a/src/Squidex.Core/Schemas/Schema.cs
+++ b/src/Squidex.Core/Schemas/Schema.cs
@@ -10,11 +10,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
-using System.Threading.Tasks;
using Microsoft.OData.Edm.Library;
-using Newtonsoft.Json.Linq;
using NJsonSchema;
-using Squidex.Core.Contents;
using Squidex.Infrastructure;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@@ -22,7 +19,7 @@ using Squidex.Infrastructure;
namespace Squidex.Core.Schemas
{
- public sealed class Schema : Cloneable
+ public sealed class Schema : CloneableBase
{
private readonly string name;
private readonly SchemaProperties properties;
@@ -189,177 +186,21 @@ namespace Squidex.Core.Schemas
return edmType;
}
- public JsonSchema4 BuildSchema(HashSet languages, Func schemaResolver)
+ public JsonSchema4 BuildJsonSchema(HashSet languages, Func schemaResolver)
{
Guard.NotEmpty(languages, nameof(languages));
Guard.NotNull(schemaResolver, nameof(schemaResolver));
var schemaName = Name.ToPascalCase();
- var schema = new JsonSchema4 { Id = schemaName, Type = JsonObjectType.Object };
+ var schema = new JsonSchema4 { Type = JsonObjectType.Object };
foreach (var field in fieldsByName.Values.Where(x => !x.IsHidden))
{
- field.AddToSchema(schema, languages, schemaName, schemaResolver);
+ field.AddToJsonSchema(schema, languages, schemaName, schemaResolver);
}
return schema;
}
-
- public async Task ValidatePartialAsync(ContentData data, IList errors, HashSet languages)
- {
- Guard.NotNull(data, nameof(data));
- Guard.NotNull(errors, nameof(errors));
-
- foreach (var fieldData in data)
- {
- if (!fieldsByName.TryGetValue(fieldData.Key, out Field field))
- {
- errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
- }
- else
- {
- var fieldErrors = new List();
-
- if (field.RawProperties.IsLocalizable)
- {
- foreach (var languageValue in fieldData.Value)
- {
- if (!Language.TryGetLanguage(languageValue.Key, out Language language))
- {
- fieldErrors.Add($"{field.Name} has an invalid language '{languageValue.Key}'");
- }
- else if (!languages.Contains(language))
- {
- fieldErrors.Add($"{field.Name} has an unsupported language '{languageValue.Key}'");
- }
- else
- {
- await field.ValidateAsync(languageValue.Value, fieldErrors, language);
- }
- }
- }
- else
- {
- if (fieldData.Value.Keys.Any(x => x != Language.Invariant.Iso2Code))
- {
- fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
- }
-
- if (fieldData.Value.TryGetValue(Language.Invariant.Iso2Code, out JToken value))
- {
- await field.ValidateAsync(value, fieldErrors);
- }
- }
-
- foreach (var error in fieldErrors)
- {
- errors.Add(new ValidationError(error, field.Name));
- }
- }
- }
- }
-
- public async Task ValidateAsync(ContentData data, IList errors, HashSet languages)
- {
- Guard.NotNull(data, nameof(data));
- Guard.NotNull(errors, nameof(errors));
- Guard.NotEmpty(languages, nameof(languages));
-
- ValidateUnknownFields(data, errors);
-
- foreach (var field in fieldsByName.Values)
- {
- var fieldErrors = new List();
- var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
-
- if (field.RawProperties.IsLocalizable)
- {
- await ValidateLocalizableFieldAsync(languages, fieldData, fieldErrors, field);
- }
- else
- {
- await ValidateNonLocalizableField(fieldData, fieldErrors, field);
- }
-
- foreach (var error in fieldErrors)
- {
- errors.Add(new ValidationError(error, field.Name));
- }
- }
- }
-
- private void ValidateUnknownFields(ContentData data, IList errors)
- {
- foreach (var fieldData in data)
- {
- if (!fieldsByName.ContainsKey(fieldData.Key))
- {
- errors.Add(new ValidationError($"{fieldData.Key} is not a known field", fieldData.Key));
- }
- }
- }
-
- private static async Task ValidateLocalizableFieldAsync(HashSet languages, ContentFieldData fieldData, List fieldErrors, Field field)
- {
- foreach (var valueLanguage in fieldData.Keys)
- {
- if (!Language.TryGetLanguage(valueLanguage, out Language language))
- {
- fieldErrors.Add($"{field.Name} has an invalid language '{valueLanguage}'");
- }
- else if (!languages.Contains(language))
- {
- fieldErrors.Add($"{field.Name} has an unsupported language '{valueLanguage}'");
- }
- }
-
- foreach (var language in languages)
- {
- var value = fieldData.GetOrCreate(language.Iso2Code, k => JValue.CreateNull());
-
- await field.ValidateAsync(value, fieldErrors, language);
- }
- }
-
- private static async Task ValidateNonLocalizableField(ContentFieldData fieldData, List fieldErrors, Field field)
- {
- if (fieldData.Keys.Any(x => x != Language.Invariant.Iso2Code))
- {
- fieldErrors.Add($"{field.Name} can only contain a single entry for invariant language ({Language.Invariant.Iso2Code})");
- }
-
- var value = fieldData.GetOrCreate(Language.Invariant.Iso2Code, k => JValue.CreateNull());
-
- await field.ValidateAsync(value, fieldErrors);
- }
-
- public void Enrich(ContentData data, HashSet languages)
- {
- Guard.NotNull(data, nameof(data));
- Guard.NotEmpty(languages, nameof(languages));
-
- foreach (var field in fieldsByName.Values)
- {
- var fieldData = data.GetOrCreate(field.Name, k => new ContentFieldData());
-
- if (field.RawProperties.IsLocalizable)
- {
- foreach (var language in languages)
- {
- field.Enrich(fieldData, language);
- }
- }
- else
- {
- field.Enrich(fieldData, Language.Invariant);
- }
-
- if (fieldData.Count > 0)
- {
- data.AddField(field.Name, fieldData);
- }
- }
- }
}
}
\ No newline at end of file
diff --git a/src/Squidex.Core/Schemas/SchemaProperties.cs b/src/Squidex.Core/Schemas/SchemaProperties.cs
index 25ba80c67..16e3a552f 100644
--- a/src/Squidex.Core/Schemas/SchemaProperties.cs
+++ b/src/Squidex.Core/Schemas/SchemaProperties.cs
@@ -8,7 +8,7 @@
namespace Squidex.Core.Schemas
{
- public sealed class SchemaProperties : NamedElementProperties
+ public sealed class SchemaProperties : NamedElementPropertiesBase
{
}
}
\ No newline at end of file
diff --git a/src/Squidex.Core/Schemas/StringField.cs b/src/Squidex.Core/Schemas/StringField.cs
index 5114bf439..cbbd99307 100644
--- a/src/Squidex.Core/Schemas/StringField.cs
+++ b/src/Squidex.Core/Schemas/StringField.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -47,7 +48,7 @@ namespace Squidex.Core.Schemas
}
}
- protected override void PrepareJsonSchema(JsonProperty jsonProperty)
+ protected override void PrepareJsonSchema(JsonProperty jsonProperty, Func schemaResolver)
{
jsonProperty.Type = JsonObjectType.String;
@@ -65,7 +66,7 @@ namespace Squidex.Core.Schemas
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, !Properties.IsRequired);
}
- protected override object ConvertValue(JToken value)
+ public override object ConvertValue(JToken value)
{
return value.ToString();
}
diff --git a/src/Squidex.Core/Schemas/StringFieldEditor.cs b/src/Squidex.Core/Schemas/StringFieldEditor.cs
index fb92c7f71..6a222e401 100644
--- a/src/Squidex.Core/Schemas/StringFieldEditor.cs
+++ b/src/Squidex.Core/Schemas/StringFieldEditor.cs
@@ -10,9 +10,9 @@ namespace Squidex.Core.Schemas
{
public enum StringFieldEditor
{
- Dropdown,
Input,
Markdown,
+ Dropdown,
Radio,
RichText,
TextArea
diff --git a/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs b/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs
index 1179d56da..0a7402e32 100644
--- a/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/AllowedValuesValidator.cs
@@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
-using System.Collections.Generic;
+using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@@ -25,7 +25,7 @@ namespace Squidex.Core.Schemas.Validators
this.allowedValues = allowedValues;
}
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
if (value == null)
{
@@ -36,7 +36,7 @@ namespace Squidex.Core.Schemas.Validators
if (!allowedValues.Contains(typedValue))
{
- errors.Add(" is not an allowed value");
+ addError(" is not an allowed value");
}
return TaskHelper.Done;
diff --git a/src/Squidex.Core/Schemas/IValidator.cs b/src/Squidex.Core/Schemas/Validators/IValidator.cs
similarity index 75%
rename from src/Squidex.Core/Schemas/IValidator.cs
rename to src/Squidex.Core/Schemas/Validators/IValidator.cs
index 405c0bd01..a604e9a70 100644
--- a/src/Squidex.Core/Schemas/IValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/IValidator.cs
@@ -6,13 +6,13 @@
// All rights reserved.
// ==========================================================================
-using System.Collections.Generic;
+using System;
using System.Threading.Tasks;
-namespace Squidex.Core.Schemas
+namespace Squidex.Core.Schemas.Validators
{
public interface IValidator
{
- Task ValidateAsync(object value, ICollection errors);
+ Task ValidateAsync(object value, Action addError);
}
}
diff --git a/src/Squidex.Core/Schemas/Validators/PatternValidator.cs b/src/Squidex.Core/Schemas/Validators/PatternValidator.cs
index 45401d8f7..aa806be54 100644
--- a/src/Squidex.Core/Schemas/Validators/PatternValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/PatternValidator.cs
@@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
-using System.Collections.Generic;
+using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@@ -28,24 +28,20 @@ namespace Squidex.Core.Schemas.Validators
regex = new Regex("^" + pattern + "$");
}
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
- var stringValue = value as string;
-
- if (stringValue == null)
- {
- return TaskHelper.Done;
- }
-
- if (!regex.IsMatch(stringValue))
+ if (value is string stringValue)
{
- if (string.IsNullOrWhiteSpace(errorMessage))
- {
- errors.Add(" is not valid");
- }
- else
+ if (!regex.IsMatch(stringValue))
{
- errors.Add(errorMessage);
+ if (string.IsNullOrWhiteSpace(errorMessage))
+ {
+ addError(" is not valid");
+ }
+ else
+ {
+ addError(errorMessage);
+ }
}
}
diff --git a/src/Squidex.Core/Schemas/Validators/RangeValidator.cs b/src/Squidex.Core/Schemas/Validators/RangeValidator.cs
index 2c512b02c..0b5669228 100644
--- a/src/Squidex.Core/Schemas/Validators/RangeValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/RangeValidator.cs
@@ -7,7 +7,6 @@
// ==========================================================================
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@@ -29,7 +28,7 @@ namespace Squidex.Core.Schemas.Validators
this.max = max;
}
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
if (value == null)
{
@@ -40,12 +39,12 @@ namespace Squidex.Core.Schemas.Validators
if (min.HasValue && typedValue.CompareTo(min.Value) < 0)
{
- errors.Add($" must be greater than '{min}'");
+ addError($" must be greater than '{min}'");
}
if (max.HasValue && typedValue.CompareTo(max.Value) > 0)
{
- errors.Add($" must be less than '{max}'");
+ addError($" must be less than '{max}'");
}
return TaskHelper.Done;
diff --git a/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs b/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs
index ca3941f2a..f64d61c9a 100644
--- a/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/RequiredStringValidator.cs
@@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
-using System.Collections.Generic;
+using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@@ -21,7 +21,7 @@ namespace Squidex.Core.Schemas.Validators
this.validateEmptyStrings = validateEmptyStrings;
}
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
if (value != null && !(value is string))
{
@@ -32,7 +32,7 @@ namespace Squidex.Core.Schemas.Validators
if (valueAsString == null || (validateEmptyStrings && string.IsNullOrWhiteSpace(valueAsString)))
{
- errors.Add(" is required");
+ addError(" is required");
}
return TaskHelper.Done;
diff --git a/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs b/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs
index 1938726d9..5e86ad0cb 100644
--- a/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/RequiredValidator.cs
@@ -6,7 +6,7 @@
// All rights reserved.
// ==========================================================================
-using System.Collections.Generic;
+using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
@@ -14,11 +14,11 @@ namespace Squidex.Core.Schemas.Validators
{
public class RequiredValidator : IValidator
{
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
if (value == null)
{
- errors.Add(" is required");
+ addError(" is required");
}
return TaskHelper.Done;
diff --git a/src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs b/src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs
index b1e01fb61..73360646c 100644
--- a/src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs
+++ b/src/Squidex.Core/Schemas/Validators/StringLengthValidator.cs
@@ -7,10 +7,11 @@
// ==========================================================================
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.Tasks;
+// ReSharper disable InvertIf
+
namespace Squidex.Core.Schemas.Validators
{
public class StringLengthValidator : IValidator
@@ -29,23 +30,19 @@ namespace Squidex.Core.Schemas.Validators
this.maxLength = maxLength;
}
- public Task ValidateAsync(object value, ICollection errors)
+ public Task ValidateAsync(object value, Action addError)
{
- var stringValue = value as string;
-
- if (stringValue == null)
- {
- return TaskHelper.Done;
- }
-
- if (minLength.HasValue && stringValue.Length < minLength.Value)
- {
- errors.Add($" must have more than '{minLength}' characters");
- }
-
- if (maxLength.HasValue && stringValue.Length > maxLength.Value)
+ if (value is string stringValue)
{
- errors.Add($" must have less than '{maxLength}' characters");
+ if (minLength.HasValue && stringValue.Length < minLength.Value)
+ {
+ addError($" must have more than '{minLength}' characters");
+ }
+
+ if (maxLength.HasValue && stringValue.Length > maxLength.Value)
+ {
+ addError($" must have less than '{maxLength}' characters");
+ }
}
return TaskHelper.Done;
diff --git a/src/Squidex.Core/Squidex.Core.csproj b/src/Squidex.Core/Squidex.Core.csproj
index 8080fb0ce..05847ae26 100644
--- a/src/Squidex.Core/Squidex.Core.csproj
+++ b/src/Squidex.Core/Squidex.Core.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/Squidex.Events/.gitignore b/src/Squidex.Events/.gitignore
deleted file mode 100644
index 0ca27f04e..000000000
--- a/src/Squidex.Events/.gitignore
+++ /dev/null
@@ -1,234 +0,0 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-build/
-bld/
-[Bb]in/
-[Oo]bj/
-
-# Visual Studio 2015 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# DNX
-project.lock.json
-artifacts/
-
-*_i.c
-*_p.c
-*_i.h
-*.ilk
-*.meta
-*.obj
-*.pch
-*.pdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# TODO: Comment the next line if you want to checkin your web deploy settings
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/packages/*
-# except build/, which is used as an MSBuild target.
-!**/packages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Microsoft Azure ApplicationInsights config file
-ApplicationInsights.config
-
-# Windows Store app package directory
-AppPackages/
-BundleArtifacts/
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.pfx
-*.publishsettings
-node_modules/
-orleans.codegen.cs
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-
-# FAKE - F# Make
-.fake/
diff --git a/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs b/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs
index 20b3f1b3e..c66e8caac 100644
--- a/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs
+++ b/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs
@@ -12,7 +12,7 @@ using Squidex.Core.Schemas;
namespace Squidex.Events.Schemas.Utils
{
- public class SchemaEventDispatcher
+ public static class SchemaEventDispatcher
{
public static Schema Dispatch(SchemaCreated @event)
{
diff --git a/src/Squidex.Events/SquidexEvent.cs b/src/Squidex.Events/SquidexEvent.cs
index e84658bef..3b121958e 100644
--- a/src/Squidex.Events/SquidexEvent.cs
+++ b/src/Squidex.Events/SquidexEvent.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// SquidexEvent.cs
+// SquidexEventBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
diff --git a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
index d51cfc9c7..b7d3ff079 100644
--- a/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
+++ b/src/Squidex.Infrastructure.MongoDb/EventStore/MongoEventStore.cs
@@ -69,15 +69,17 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
{
await Collection.Find(x => x.EventStream == streamName).ForEachAsync(commit =>
{
- var position = commit.EventStreamOffset;
+ var eventNumber = commit.EventsOffset;
+ var eventStreamNumber = commit.EventStreamOffset;
foreach (var @event in commit.Events)
{
- var eventData = SimpleMapper.Map(@event, new EventData());
+ eventNumber++;
+ eventStreamNumber++;
- observer.OnNext(new StoredEvent(position, eventData));
+ var eventData = SimpleMapper.Map(@event, new EventData());
- position++;
+ observer.OnNext(new StoredEvent(eventNumber, eventStreamNumber, eventData));
}
}, ct);
});
@@ -92,16 +94,18 @@ namespace Squidex.Infrastructure.MongoDb.EventStore
await Collection.Find(x => x.EventsOffset >= commitOffset).SortBy(x => x.EventsOffset).ForEachAsync(commit =>
{
var eventNumber = commit.EventsOffset;
+ var eventStreamNumber = commit.EventStreamOffset;
foreach (var @event in commit.Events)
{
eventNumber++;
+ eventStreamNumber++;
if (eventNumber > lastReceivedEventNumber)
{
var eventData = SimpleMapper.Map(@event, new EventData());
- observer.OnNext(new StoredEvent(eventNumber, eventData));
+ observer.OnNext(new StoredEvent(eventNumber, eventStreamNumber, eventData));
}
}
}, ct);
diff --git a/src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs b/src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs
index bc9046d87..c5de2f735 100644
--- a/src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs
+++ b/src/Squidex.Infrastructure.MongoDb/InstantSerializer.cs
@@ -9,6 +9,7 @@
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using NodaTime;
+using System;
// ReSharper disable InvertIf
@@ -16,26 +17,16 @@ namespace Squidex.Infrastructure.MongoDb
{
public sealed class InstantSerializer : SerializerBase, IBsonPolymorphicSerializer
{
- private static bool isRegistered;
- private static readonly object LockObject = new object();
+ private static readonly Lazy Registerer = new Lazy(() =>
+ {
+ BsonSerializer.RegisterSerializer(new InstantSerializer());
+
+ return true;
+ });
public static bool Register()
{
- if (!isRegistered)
- {
- lock (LockObject)
- {
- if (!isRegistered)
- {
- BsonSerializer.RegisterSerializer(new InstantSerializer());
-
- isRegistered = true;
- return true;
- }
- }
- }
-
- return false;
+ return !Registerer.IsValueCreated && Registerer.Value;
}
public bool IsDiscriminatorCompatibleWithObjectSerializer
diff --git a/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs b/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
index 481e55b2f..b0ec9002a 100644
--- a/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
+++ b/src/Squidex.Infrastructure.MongoDb/MongoRepositoryBase.cs
@@ -11,6 +11,7 @@ using System.Globalization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
+using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.MongoDb
{
@@ -29,7 +30,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
- protected ProjectionDefinitionBuilder Projection
+ protected static ProjectionDefinitionBuilder Projection
{
get
{
@@ -37,7 +38,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
- protected SortDefinitionBuilder Sort
+ protected static SortDefinitionBuilder Sort
{
get
{
@@ -45,7 +46,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
- protected UpdateDefinitionBuilder Update
+ protected static UpdateDefinitionBuilder Update
{
get
{
@@ -53,7 +54,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
- protected FilterDefinitionBuilder Filter
+ protected static FilterDefinitionBuilder Filter
{
get
{
@@ -61,7 +62,7 @@ namespace Squidex.Infrastructure.MongoDb
}
}
- protected IndexKeysDefinitionBuilder IndexKeys
+ protected static IndexKeysDefinitionBuilder IndexKeys
{
get
{
@@ -127,7 +128,7 @@ namespace Squidex.Infrastructure.MongoDb
protected virtual Task SetupCollectionAsync(IMongoCollection collection)
{
- return Task.FromResult(true);
+ return TaskHelper.Done;
}
public virtual Task ClearAsync()
@@ -135,7 +136,7 @@ namespace Squidex.Infrastructure.MongoDb
return Collection.DeleteManyAsync(new BsonDocument());
}
- public async Task TryDropCollectionAsync()
+ public async Task DropCollectionIfExistsAsync()
{
try
{
diff --git a/src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs b/src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs
index 553a6a728..723e9d2ab 100644
--- a/src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs
+++ b/src/Squidex.Infrastructure.MongoDb/RefTokenSerializer.cs
@@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
+using System;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
@@ -15,26 +16,16 @@ namespace Squidex.Infrastructure.MongoDb
{
public class RefTokenSerializer : SerializerBase
{
- private static bool isRegistered;
- private static readonly object LockObject = new object();
+ private static readonly Lazy Registerer = new Lazy(() =>
+ {
+ BsonSerializer.RegisterSerializer(new RefTokenSerializer());
+
+ return true;
+ });
public static bool Register()
{
- if (!isRegistered)
- {
- lock (LockObject)
- {
- if (!isRegistered)
- {
- BsonSerializer.RegisterSerializer(new RefTokenSerializer());
-
- isRegistered = true;
- return true;
- }
- }
- }
-
- return false;
+ return !Registerer.IsValueCreated && Registerer.Value;
}
public override RefToken Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
diff --git a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
index 2d95e66be..a2f2b7df3 100644
--- a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
+++ b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj
@@ -10,6 +10,6 @@
-
+
diff --git a/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs b/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs
index ca7ad3a3b..1fca8ec32 100644
--- a/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs
+++ b/src/Squidex.Infrastructure.Redis/RedisInfrastructureErrors.cs
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging;
namespace Squidex.Infrastructure.Redis
{
- public class RedisInfrastructureErrors
+ public static class RedisInfrastructureErrors
{
public static readonly EventId InvalidatingReceivedFailed = new EventId(50001, "InvalidingReceivedFailed");
diff --git a/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj b/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj
index 68d07fc7b..98a461896 100644
--- a/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj
+++ b/src/Squidex.Infrastructure.Redis/Squidex.Infrastructure.Redis.csproj
@@ -10,7 +10,7 @@
-
-
+
+
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
index 795695484..ecab8169c 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
@@ -7,7 +7,6 @@
// ==========================================================================
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Events;
@@ -17,7 +16,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
private readonly IDomainObjectRepository domainObjectRepository;
private readonly IDomainObjectFactory domainObjectFactory;
- private readonly IEnumerable eventProcessors;
public IDomainObjectRepository Repository
{
@@ -31,55 +29,72 @@ namespace Squidex.Infrastructure.CQRS.Commands
public AggregateHandler(
IDomainObjectFactory domainObjectFactory,
- IDomainObjectRepository domainObjectRepository,
- IEnumerable eventProcessors)
+ IDomainObjectRepository domainObjectRepository)
{
- Guard.NotNull(eventProcessors, nameof(eventProcessors));
Guard.NotNull(domainObjectFactory, nameof(domainObjectFactory));
Guard.NotNull(domainObjectRepository, nameof(domainObjectRepository));
this.domainObjectFactory = domainObjectFactory;
this.domainObjectRepository = domainObjectRepository;
-
- this.eventProcessors = eventProcessors;
}
- public async Task CreateAsync(IAggregateCommand command, Func creator) where T : class, IAggregate
+ public async Task CreateAsync(CommandContext context, Func creator) where T : class, IAggregate
{
Guard.NotNull(creator, nameof(creator));
- Guard.NotNull(command, nameof(command));
- Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
+ Guard.NotNull(context, nameof(context));
- var aggregate = domainObjectFactory.CreateNew(command.AggregateId);
+ var aggregateCommand = GetCommand(context);
+ var aggregate = (T)domainObjectFactory.CreateNew(typeof(T), aggregateCommand.AggregateId);
await creator(aggregate);
- await Save(command, aggregate);
+ await SaveAsync(aggregate);
+
+ if (!context.IsHandled)
+ {
+ context.Succeed(new EntityCreatedResult(aggregate.Id, aggregate.Version));
+ }
}
- public async Task UpdateAsync(IAggregateCommand command, Func updater) where T : class, IAggregate
+ public async Task UpdateAsync(CommandContext context, Func updater) where T : class, IAggregate
{
Guard.NotNull(updater, nameof(updater));
- Guard.NotNull(command, nameof(command));
- Guard.NotEmpty(command.AggregateId, nameof(command.AggregateId));
+ Guard.NotNull(context, nameof(context));
- var aggregate = await domainObjectRepository.GetByIdAsync(command.AggregateId);
+ var aggregateCommand = GetCommand(context);
+ var aggregate = await domainObjectRepository.GetByIdAsync(aggregateCommand.AggregateId, aggregateCommand.ExpectedVersion);
await updater(aggregate);
- await Save(command, aggregate);
+ await SaveAsync(aggregate);
+
+ if (!context.IsHandled)
+ {
+ context.Succeed(new EntitySavedResult(aggregate.Version));
+ }
+ }
+
+ private static IAggregateCommand GetCommand(CommandContext context)
+ {
+ var command = context.Command as IAggregateCommand;
+
+ if (command == null)
+ {
+ throw new ArgumentException("Context must have an aggregate command.", nameof(context));
+ }
+
+ Guard.NotEmpty(command.AggregateId, "context.Command.AggregateId");
+
+ return command;
}
- private async Task Save(ICommand command, IAggregate aggregate)
+ private async Task SaveAsync(IAggregate aggregate)
{
var events = aggregate.GetUncomittedEvents();
foreach (var @event in events)
{
- foreach (var eventProcessor in eventProcessors)
- {
- await eventProcessor.ProcessEventAsync(@event, aggregate, command);
- }
+ @event.SetAggregateId(aggregate.Id);
}
await domainObjectRepository.SaveAsync(aggregate, events, Guid.NewGuid());
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs b/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
index 07b57e503..c56f7599a 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/CommandingExtensions.cs
@@ -14,14 +14,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public static class CommandingExtensions
{
- public static T CreateNew(this IDomainObjectFactory factory, Guid id) where T : IAggregate
+ public static Task CreateAsync(this IAggregateHandler handler, CommandContext context, Action creator) where T : class, IAggregate
{
- return (T)factory.CreateNew(typeof(T), id);
- }
-
- public static Task CreateAsync(this IAggregateHandler handler, IAggregateCommand command, Action creator) where T : class, IAggregate
- {
- return handler.CreateAsync(command, x =>
+ return handler.CreateAsync(context, x =>
{
creator(x);
@@ -29,12 +24,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
});
}
- public static Task UpdateAsync(this IAggregateHandler handler, IAggregateCommand command, Action creator) where T : class, IAggregate
+ public static Task UpdateAsync(this IAggregateHandler handler, CommandContext context, Action updater) where T : class, IAggregate
{
- return handler.UpdateAsync(command, x =>
+ return handler.UpdateAsync(context, x =>
{
- creator(x);
-
+ updater(x);
+
return TaskHelper.Done;
});
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs
index 24efb3956..1308de330 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectFactory.cs
@@ -28,7 +28,14 @@ namespace Squidex.Infrastructure.CQRS.Commands
var factoryFunctionType = typeof(DomainObjectFactoryFunction<>).MakeGenericType(type);
var factoryFunction = (Delegate)serviceProvider.GetService(factoryFunctionType);
- return (IAggregate)factoryFunction.DynamicInvoke(id);
+ var aggregate = (IAggregate)factoryFunction.DynamicInvoke(id);
+
+ if (aggregate.Version != -1)
+ {
+ throw new InvalidOperationException("Must have a version of -1");
+ }
+
+ return aggregate;
}
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
index 7c657d2e6..3c163beb2 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
@@ -39,10 +39,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
this.nameResolver = nameResolver;
}
- public async Task GetByIdAsync(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate
+ public async Task GetByIdAsync(Guid id, long? expectedVersion = null) where TDomainObject : class, IAggregate
{
- Guard.GreaterThan(version, 0, nameof(version));
-
var streamName = nameResolver.GetStreamName(typeof(TDomainObject), id);
var events = await eventStore.GetEventsAsync(streamName).ToList();
@@ -61,9 +59,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
domainObject.ApplyEvent(envelope);
}
- if (domainObject.Version != version && version < int.MaxValue)
+ if (expectedVersion != null && domainObject.Version != expectedVersion.Value)
{
- throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, version);
+ throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, expectedVersion.Value);
}
return domainObject;
@@ -76,8 +74,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id);
var versionCurrent = domainObject.Version;
- var versionBefore = versionCurrent - events.Count;
- var versionExpected = versionBefore == 0 ? -1 : versionBefore - 1;
+ var versionExpected = versionCurrent - events.Count;
var eventsToSave = events.Select(x => formatter.ToEventData(x, commitId)).ToList();
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs
index 1f8bc892b..855c11865 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/EnrichWithTimestampHandler.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using NodaTime;
+using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Commands
{
@@ -24,14 +25,12 @@ namespace Squidex.Infrastructure.CQRS.Commands
public Task HandleAsync(CommandContext context)
{
- var timestampCommand = context.Command as ITimestampCommand;
-
- if (timestampCommand != null)
+ if (context.Command is ITimestampCommand timestampCommand)
{
timestampCommand.Timestamp = clock.GetCurrentInstant();
}
- return Task.FromResult(false);
+ return TaskHelper.False;
}
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs
new file mode 100644
index 000000000..259b836df
--- /dev/null
+++ b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult.cs
@@ -0,0 +1,18 @@
+// ==========================================================================
+// EntityCreatedResult.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+namespace Squidex.Infrastructure.CQRS.Commands
+{
+ public static class EntityCreatedResult
+ {
+ public static EntityCreatedResult Create(T idOrValue, long version)
+ {
+ return new EntityCreatedResult(idOrValue, version);
+ }
+ }
+}
diff --git a/src/Squidex/Config/Web/WebpackServices.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs
similarity index 51%
rename from src/Squidex/Config/Web/WebpackServices.cs
rename to src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs
index 728fe4c90..7b56d620c 100644
--- a/src/Squidex/Config/Web/WebpackServices.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/EntityCreatedResult_T.cs
@@ -1,23 +1,21 @@
// ==========================================================================
-// WebpackServices.cs
+// EntityCreatedResult_T.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
-using Microsoft.Extensions.DependencyInjection;
-using Squidex.Pipeline;
-
-namespace Squidex.Config.Web
+namespace Squidex.Infrastructure.CQRS.Commands
{
- public static class WebpackServices
+ public sealed class EntityCreatedResult : EntitySavedResult
{
- public static IServiceCollection AddWebpackBuilder(this IServiceCollection services)
- {
- services.AddSingleton();
+ public T IdOrValue { get; }
- return services;
+ public EntityCreatedResult(T idOrValue, long version)
+ : base(version)
+ {
+ IdOrValue = idOrValue;
}
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs b/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs
new file mode 100644
index 000000000..deef50b7c
--- /dev/null
+++ b/src/Squidex.Infrastructure/CQRS/Commands/EntitySavedResult.cs
@@ -0,0 +1,20 @@
+// ==========================================================================
+// EntitySavedResult.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+namespace Squidex.Infrastructure.CQRS.Commands
+{
+ public class EntitySavedResult
+ {
+ public long Version { get; }
+
+ public EntitySavedResult(long version)
+ {
+ Version = version;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs
index 0ebafa736..5d7ca75bb 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/IAggregateHandler.cs
@@ -13,8 +13,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface IAggregateHandler
{
- Task CreateAsync(IAggregateCommand command, Func creator) where T : class, IAggregate;
+ Task CreateAsync(CommandContext context, Func creator) where T : class, IAggregate;
- Task UpdateAsync(IAggregateCommand command, Func updater) where T : class, IAggregate;
+ Task UpdateAsync(CommandContext context, Func updater) where T : class, IAggregate;
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs b/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs
index c1834662e..fb3516b3b 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/ICommand.cs
@@ -10,5 +10,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface ICommand
{
+ long? ExpectedVersion { get; set; }
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs b/src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
index 925076952..2aa0ee679 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
@@ -15,7 +15,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface IDomainObjectRepository
{
- Task GetByIdAsync(Guid id, int version = int.MaxValue) where TDomainObject : class, IAggregate;
+ Task GetByIdAsync(Guid id, long? expectedVersion = null) where TDomainObject : class, IAggregate;
Task SaveAsync(IAggregate domainObject, ICollection> events, Guid commitId);
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
index e75660e1d..0e31d7548 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/LogExceptionHandler.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Squidex.Infrastructure.Tasks;
// ReSharper disable InvertIf
@@ -36,7 +37,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
logger.LogCritical(InfrastructureErrors.CommandUnknown, exception, "Unknown command {0}", context.Command);
}
- return Task.FromResult(false);
+ return TaskHelper.False;
}
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs b/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs
index 37a4e6d00..d95a02cd6 100644
--- a/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs
+++ b/src/Squidex.Infrastructure/CQRS/Commands/LogExecutingHandler.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
+using Squidex.Infrastructure.Tasks;
namespace Squidex.Infrastructure.CQRS.Commands
{
@@ -24,7 +25,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
logger.LogInformation("Handling {0} command", context.Command);
- return Task.FromResult(false);
+ return TaskHelper.False;
}
}
}
diff --git a/src/Squidex.Infrastructure/CQRS/DomainObject.cs b/src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs
similarity index 91%
rename from src/Squidex.Infrastructure/CQRS/DomainObject.cs
rename to src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs
index b9130fedf..b8e1ceb30 100644
--- a/src/Squidex.Infrastructure/CQRS/DomainObject.cs
+++ b/src/Squidex.Infrastructure/CQRS/DomainObjectBase.cs
@@ -12,7 +12,7 @@ using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Infrastructure.CQRS
{
- public abstract class DomainObject : IAggregate, IEquatable
+ public abstract class DomainObjectBase : IAggregate, IEquatable
{
private readonly List> uncomittedEvents = new List>();
private readonly Guid id;
@@ -28,10 +28,10 @@ namespace Squidex.Infrastructure.CQRS
get { return id; }
}
- protected DomainObject(Guid id, int version)
+ protected DomainObjectBase(Guid id, int version)
{
Guard.NotEmpty(id, nameof(id));
- Guard.GreaterEquals(version, 0, nameof(version));
+ Guard.GreaterEquals(version, -1, nameof(version));
this.id = id;
diff --git a/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs b/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs
similarity index 81%
rename from src/Squidex.Infrastructure/CQRS/CommonHeaders.cs
rename to src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs
index 9274e62e2..617123666 100644
--- a/src/Squidex.Infrastructure/CQRS/CommonHeaders.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/CommonHeaders.cs
@@ -6,9 +6,9 @@
// All rights reserved.
// ==========================================================================
-namespace Squidex.Infrastructure.CQRS
+namespace Squidex.Infrastructure.CQRS.Events
{
- public sealed class CommonHeaders
+ public static class CommonHeaders
{
public static readonly string AggregateId = "AggregateId";
@@ -18,6 +18,8 @@ namespace Squidex.Infrastructure.CQRS
public static readonly string EventNumber = "EventNumber";
+ public static readonly string EventStreamNumber = "EventStreamNumber";
+
public static readonly string Timestamp = "Timestamp";
public static readonly string Actor = "Actor";
diff --git a/src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs b/src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs
index 46d0945ea..afd527615 100644
--- a/src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/DefaultMemoryEventNotifier.cs
@@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class DefaultMemoryEventNotifier : IEventNotifier
{
- private readonly string ChannelName = typeof(DefaultMemoryEventNotifier).Name;
+ private static readonly string ChannelName = typeof(DefaultMemoryEventNotifier).Name;
private readonly IPubSub invalidator;
diff --git a/src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs b/src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs
deleted file mode 100644
index e8b07466d..000000000
--- a/src/Squidex.Infrastructure/CQRS/Events/EnrichWithAggregateIdProcessor.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// ==========================================================================
-// EnrichWithAggregateIdProcessor.cs
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex Group
-// All rights reserved.
-// ==========================================================================
-
-using System.Threading.Tasks;
-using Squidex.Infrastructure.CQRS.Commands;
-using Squidex.Infrastructure.Tasks;
-
-namespace Squidex.Infrastructure.CQRS.Events
-{
- public sealed class EnrichWithAggregateIdProcessor : IEventProcessor
- {
- public Task ProcessEventAsync(Envelope @event, IAggregate aggregate, ICommand command)
- {
- var aggregateCommand = command as IAggregateCommand;
-
- if (aggregateCommand != null)
- {
- @event.SetAggregateId(aggregateCommand.AggregateId);
- }
-
- return TaskHelper.Done;
- }
- }
-}
diff --git a/src/Squidex.Infrastructure/CQRS/Envelope.cs b/src/Squidex.Infrastructure/CQRS/Events/Envelope.cs
similarity index 97%
rename from src/Squidex.Infrastructure/CQRS/Envelope.cs
rename to src/Squidex.Infrastructure/CQRS/Events/Envelope.cs
index 79588727f..e2bf2aa45 100644
--- a/src/Squidex.Infrastructure/CQRS/Envelope.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/Envelope.cs
@@ -9,7 +9,7 @@
using System;
using NodaTime;
-namespace Squidex.Infrastructure.CQRS
+namespace Squidex.Infrastructure.CQRS.Events
{
public class Envelope where TPayload : class
{
diff --git a/src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeExtensions.cs
similarity index 83%
rename from src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs
rename to src/Squidex.Infrastructure/CQRS/Events/EnvelopeExtensions.cs
index 27a757517..80886571a 100644
--- a/src/Squidex.Infrastructure/CQRS/EnvelopeExtensions.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeExtensions.cs
@@ -10,7 +10,7 @@ using System;
using System.Globalization;
using NodaTime;
-namespace Squidex.Infrastructure.CQRS
+namespace Squidex.Infrastructure.CQRS.Events
{
public static class EnvelopeExtensions
{
@@ -26,6 +26,18 @@ namespace Squidex.Infrastructure.CQRS
return envelope;
}
+ public static long EventStreamNumber(this EnvelopeHeaders headers)
+ {
+ return headers[CommonHeaders.EventStreamNumber].ToInt32(CultureInfo.InvariantCulture);
+ }
+
+ public static Envelope SetEventStreamNumber(this Envelope envelope, long value) where T : class
+ {
+ envelope.Headers.Set(CommonHeaders.EventStreamNumber, value);
+
+ return envelope;
+ }
+
public static Guid CommitId(this EnvelopeHeaders headers)
{
return headers[CommonHeaders.CommitId].ToGuid(CultureInfo.InvariantCulture);
diff --git a/src/Squidex.Infrastructure/CQRS/EnvelopeHeaders.cs b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs
similarity index 95%
rename from src/Squidex.Infrastructure/CQRS/EnvelopeHeaders.cs
rename to src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs
index 3747ddbbb..f95580f9d 100644
--- a/src/Squidex.Infrastructure/CQRS/EnvelopeHeaders.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/EnvelopeHeaders.cs
@@ -5,7 +5,8 @@
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
-namespace Squidex.Infrastructure.CQRS
+
+namespace Squidex.Infrastructure.CQRS.Events
{
public sealed class EnvelopeHeaders : PropertiesBag
{
diff --git a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
index d693f040f..aca57463c 100644
--- a/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/EventReceiver.cs
@@ -18,7 +18,7 @@ using Squidex.Infrastructure.Timers;
namespace Squidex.Infrastructure.CQRS.Events
{
- public sealed class EventReceiver : DisposableObject
+ public sealed class EventReceiver : DisposableObjectBase
{
private readonly EventDataFormatter formatter;
private readonly IEventStore eventStore;
@@ -176,6 +176,7 @@ namespace Squidex.Infrastructure.CQRS.Events
var @event = formatter.Parse(storedEvent.Data);
@event.SetEventNumber(storedEvent.EventNumber);
+ @event.SetEventStreamNumber(storedEvent.EventStreamNumber);
return @event;
}
diff --git a/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs b/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs
index 4f8f04d6e..547956c13 100644
--- a/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs
+++ b/src/Squidex.Infrastructure/CQRS/Events/StoredEvent.cs
@@ -11,6 +11,7 @@ namespace Squidex.Infrastructure.CQRS.Events
public sealed class StoredEvent
{
private readonly long eventNumber;
+ private readonly long eventStreamNumber;
private readonly EventData data;
public long EventNumber
@@ -18,18 +19,23 @@ namespace Squidex.Infrastructure.CQRS.Events
get { return eventNumber; }
}
+ public long EventStreamNumber
+ {
+ get { return eventStreamNumber; }
+ }
+
public EventData Data
{
get { return data; }
}
- public StoredEvent(long eventNumber, EventData data)
+ public StoredEvent(long eventNumber, long eventStreamNumber, EventData data)
{
Guard.NotNull(data, nameof(data));
this.data = data;
-
this.eventNumber = eventNumber;
+ this.eventStreamNumber = eventStreamNumber;
}
}
}
diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs
index 1050e4a68..0b177c816 100644
--- a/src/Squidex.Infrastructure/CollectionExtensions.cs
+++ b/src/Squidex.Infrastructure/CollectionExtensions.cs
@@ -30,7 +30,7 @@ namespace Squidex.Infrastructure
{
if (item != null)
{
- hashCode = hashCode * 23 + item.GetHashCode();
+ hashCode = hashCode * 23 + comparer.GetHashCode(item);
}
}
diff --git a/src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs b/src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs
index ad64b3f78..19f76be73 100644
--- a/src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs
+++ b/src/Squidex.Infrastructure/Dispatching/ActionContextDispatcherFactory.cs
@@ -13,7 +13,7 @@ using System.Reflection;
namespace Squidex.Infrastructure.Dispatching
{
- internal class ActionContextDispatcherFactory
+ internal static class ActionContextDispatcherFactory
{
public static Tuple> CreateActionHandler(MethodInfo methodInfo)
{
diff --git a/src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs b/src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs
index 35d5113aa..04de5fe19 100644
--- a/src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs
+++ b/src/Squidex.Infrastructure/Dispatching/ActionDispatcherFactory.cs
@@ -13,7 +13,7 @@ using System.Reflection;
namespace Squidex.Infrastructure.Dispatching
{
- internal class ActionDispatcherFactory
+ internal static class ActionDispatcherFactory
{
public static Tuple> CreateActionHandler(MethodInfo methodInfo)
{
diff --git a/src/Squidex.Infrastructure/DisposableObject.cs b/src/Squidex.Infrastructure/DisposableObjectBase.cs
similarity index 94%
rename from src/Squidex.Infrastructure/DisposableObject.cs
rename to src/Squidex.Infrastructure/DisposableObjectBase.cs
index 98da1ace5..a8ead1cbd 100644
--- a/src/Squidex.Infrastructure/DisposableObject.cs
+++ b/src/Squidex.Infrastructure/DisposableObjectBase.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// EnumExtensions.cs
+// DisposableObjectBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@@ -10,7 +10,7 @@ using System;
namespace Squidex.Infrastructure
{
- public abstract class DisposableObject : IDisposable
+ public abstract class DisposableObjectBase : IDisposable
{
private readonly object disposeLock = new object();
private bool isDisposed;
diff --git a/src/Squidex.Infrastructure/DomainObjectVersionException.cs b/src/Squidex.Infrastructure/DomainObjectVersionException.cs
index 9a269cbbc..19599594d 100644
--- a/src/Squidex.Infrastructure/DomainObjectVersionException.cs
+++ b/src/Squidex.Infrastructure/DomainObjectVersionException.cs
@@ -12,20 +12,20 @@ namespace Squidex.Infrastructure
{
public class DomainObjectVersionException : DomainObjectException
{
- private readonly int currentVersion;
- private readonly int expectedVersion;
+ private readonly long currentVersion;
+ private readonly long expectedVersion;
- public int CurrentVersion
+ public long CurrentVersion
{
get { return currentVersion; }
}
- public int ExpectedVersion
+ public long ExpectedVersion
{
get { return expectedVersion; }
}
- public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion)
+ public DomainObjectVersionException(string id, Type type, long currentVersion, long expectedVersion)
: base(FormatMessage(id, type, currentVersion, expectedVersion), id, type)
{
this.currentVersion = currentVersion;
@@ -33,7 +33,7 @@ namespace Squidex.Infrastructure
this.expectedVersion = expectedVersion;
}
- private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion)
+ private static string FormatMessage(string id, Type type, long currentVersion, long expectedVersion)
{
return $"Requested version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}.";
}
diff --git a/src/Squidex.Infrastructure/Json/ConverterContractResolver.cs b/src/Squidex.Infrastructure/Json/ConverterContractResolver.cs
new file mode 100644
index 000000000..625d36fa1
--- /dev/null
+++ b/src/Squidex.Infrastructure/Json/ConverterContractResolver.cs
@@ -0,0 +1,44 @@
+// ==========================================================================
+// ConverterContractResolver.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Squidex.Infrastructure.Json
+{
+ public sealed class ConverterContractResolver : CamelCasePropertyNamesContractResolver
+ {
+ private readonly JsonConverter[] converters;
+
+ public ConverterContractResolver(params JsonConverter[] converters)
+ {
+ this.converters = converters;
+ }
+
+ protected override JsonConverter ResolveContractConverter(Type objectType)
+ {
+ var result = base.ResolveContractConverter(objectType);
+
+ if (result != null)
+ {
+ return result;
+ }
+
+ foreach (var converter in converters)
+ {
+ if (converter.CanConvert(objectType))
+ {
+ return converter;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/Json/JsonExtension.cs b/src/Squidex.Infrastructure/Json/JsonExtension.cs
new file mode 100644
index 000000000..5b3125cc3
--- /dev/null
+++ b/src/Squidex.Infrastructure/Json/JsonExtension.cs
@@ -0,0 +1,33 @@
+// ==========================================================================
+// JsonExtension.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using Newtonsoft.Json.Linq;
+
+namespace Squidex.Infrastructure.Json
+{
+ public static class JsonExtension
+ {
+ public static bool IsNull(this JToken token)
+ {
+ if (token == null)
+ {
+ return true;
+ }
+ if (token.Type == JTokenType.Null)
+ {
+ return true;
+ }
+ if (token is JValue value)
+ {
+ return value.Value == null;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Squidex.Infrastructure/Language.cs b/src/Squidex.Infrastructure/Language.cs
index 641d9fd82..b369332cc 100644
--- a/src/Squidex.Infrastructure/Language.cs
+++ b/src/Squidex.Infrastructure/Language.cs
@@ -19,7 +19,7 @@ namespace Squidex.Infrastructure
private static readonly Regex CultureRegex = new Regex("([a-z]{2})(\\-[a-z]{2})?");
private readonly string iso2Code;
private readonly string englishName;
- private static readonly Dictionary allLanguages = new Dictionary();
+ private static readonly Dictionary AllLanguagesField = new Dictionary();
public static readonly Language Invariant = AddLanguage("iv", "Invariant");
@@ -27,7 +27,7 @@ namespace Squidex.Infrastructure
{
var language = new Language(iso2Code, englishName);
- allLanguages[iso2Code] = language;
+ AllLanguagesField[iso2Code] = language;
return language;
}
@@ -38,7 +38,7 @@ namespace Squidex.Infrastructure
try
{
- return allLanguages[iso2Code];
+ return AllLanguagesField[iso2Code];
}
catch (KeyNotFoundException)
{
@@ -48,7 +48,7 @@ namespace Squidex.Infrastructure
public static IEnumerable AllLanguages
{
- get { return allLanguages.Values; }
+ get { return AllLanguagesField.Values; }
}
public string EnglishName
@@ -72,17 +72,17 @@ namespace Squidex.Infrastructure
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
- return allLanguages.ContainsKey(iso2Code);
+ return AllLanguagesField.ContainsKey(iso2Code);
}
public static bool TryGetLanguage(string iso2Code, out Language language)
{
Guard.NotNullOrEmpty(iso2Code, nameof(iso2Code));
- return allLanguages.TryGetValue(iso2Code, out language);
+ return AllLanguagesField.TryGetValue(iso2Code, out language);
}
- public static Language TryParse(string input)
+ public static Language ParseOrNull(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
diff --git a/src/Squidex.Infrastructure/Languages.cs b/src/Squidex.Infrastructure/Languages.cs
index 5fb87a0b1..0e03ff9fd 100644
--- a/src/Squidex.Infrastructure/Languages.cs
+++ b/src/Squidex.Infrastructure/Languages.cs
@@ -5,9 +5,13 @@
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
+//
+
+using System.CodeDom.Compiler;
namespace Squidex.Infrastructure
{
+ [GeneratedCode("LanguagesGenerator", "1.0")]
partial class Language
{
public static readonly Language AA = AddLanguage("aa", "Afar");
diff --git a/src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs b/src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs
index 1bdeccfe0..355dd088d 100644
--- a/src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs
+++ b/src/Squidex.Infrastructure/Reflection/PropertyAccessor.cs
@@ -26,7 +26,7 @@ namespace Squidex.Infrastructure.Reflection
}
else
{
- getMethod = x => throw new NotSupportedException();
+ getMethod = x => { throw new NotSupportedException(); };
}
if (propertyInfo.CanWrite)
@@ -35,7 +35,7 @@ namespace Squidex.Infrastructure.Reflection
}
else
{
- setMethod = (x, y) => throw new NotSupportedException();
+ setMethod = (x, y) => { throw new NotSupportedException(); };
}
}
diff --git a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs
index 1fe650780..e01fb6d88 100644
--- a/src/Squidex.Infrastructure/Security/OpenIdClaims.cs
+++ b/src/Squidex.Infrastructure/Security/OpenIdClaims.cs
@@ -8,7 +8,7 @@
namespace Squidex.Infrastructure.Security
{
- public class OpenIdClaims
+ public static class OpenIdClaims
{
///
/// Unique Identifier for the End-User at the Issuer.
diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
index 64fe379bd..b9208aeb0 100644
--- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
+++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
@@ -7,8 +7,8 @@
True
-
-
+
+
diff --git a/src/Squidex.Infrastructure/Tasks/TaskHelper.cs b/src/Squidex.Infrastructure/Tasks/TaskHelper.cs
index c0ad8286c..eb2e1c1e0 100644
--- a/src/Squidex.Infrastructure/Tasks/TaskHelper.cs
+++ b/src/Squidex.Infrastructure/Tasks/TaskHelper.cs
@@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.Tasks
public static class TaskHelper
{
public static readonly Task Done = CreateDoneTask();
+ public static readonly Task False = CreateResultTask(false);
+ public static readonly Task True = CreateResultTask(true);
private static Task CreateDoneTask()
{
@@ -22,5 +24,14 @@ namespace Squidex.Infrastructure.Tasks
return result.Task;
}
+
+ private static Task CreateResultTask(bool value)
+ {
+ var result = new TaskCompletionSource();
+
+ result.SetResult(value);
+
+ return result.Task;
+ }
}
}
\ No newline at end of file
diff --git a/src/Squidex.Infrastructure/Timers/CompletionTimer.cs b/src/Squidex.Infrastructure/Timers/CompletionTimer.cs
index 94b94b640..b4dc061bf 100644
--- a/src/Squidex.Infrastructure/Timers/CompletionTimer.cs
+++ b/src/Squidex.Infrastructure/Timers/CompletionTimer.cs
@@ -14,7 +14,7 @@ using System.Threading.Tasks;
namespace Squidex.Infrastructure.Timers
{
- public sealed class CompletionTimer : DisposableObject
+ public sealed class CompletionTimer : DisposableObjectBase
{
private readonly CancellationTokenSource disposeToken = new CancellationTokenSource();
private readonly Task runTask;
diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
index 6d280253c..7c4e34fe3 100644
--- a/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
+++ b/src/Squidex.Read.MongoDb/Apps/MongoAppEntity.cs
@@ -13,6 +13,8 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Apps;
+// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
+
namespace Squidex.Read.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoEntity, IAppEntity
@@ -25,6 +27,10 @@ namespace Squidex.Read.MongoDb.Apps
[BsonElement]
public string MasterLanguage { get; set; }
+ [BsonRequired]
+ [BsonElement]
+ public long Version { get; set; }
+
[BsonRequired]
[BsonElement]
public HashSet Languages { get; set; } = new HashSet();
diff --git a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
index de55df60f..606a28e8c 100644
--- a/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
+++ b/src/Squidex.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Squidex.Events;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
diff --git a/src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
index 6d9d4dfcf..729356ef9 100644
--- a/src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
+++ b/src/Squidex.Read.MongoDb/Contents/MongoContentEntity.cs
@@ -36,6 +36,10 @@ namespace Squidex.Read.MongoDb.Contents
[BsonElement]
public string Text { get; set; }
+ [BsonRequired]
+ [BsonElement]
+ public long Version { get; set; }
+
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
diff --git a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
index a29daffa5..99bcb9223 100644
--- a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
+++ b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs
@@ -12,12 +12,13 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.OData.Core;
using MongoDB.Driver;
-using Squidex.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.Contents;
+using Squidex.Read.Contents.Builders;
using Squidex.Read.Contents.Repositories;
using Squidex.Read.MongoDb.Contents.Visitors;
+using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
namespace Squidex.Read.MongoDb.Contents
@@ -26,7 +27,8 @@ namespace Squidex.Read.MongoDb.Contents
{
private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database;
- private readonly ISchemaProvider schemaProvider;
+ private readonly ISchemaProvider schemas;
+ private readonly EdmModelBuilder modelBuilder;
protected static IndexKeysDefinitionBuilder IndexKeys
{
@@ -36,28 +38,31 @@ namespace Squidex.Read.MongoDb.Contents
}
}
- public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemaProvider)
+ public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas, EdmModelBuilder modelBuilder)
{
Guard.NotNull(database, nameof(database));
- Guard.NotNull(schemaProvider, nameof(schemaProvider));
+ Guard.NotNull(modelBuilder, nameof(modelBuilder));
+ Guard.NotNull(schemas, nameof(schemas));
+ this.schemas = schemas;
this.database = database;
-
- this.schemaProvider = schemaProvider;
+ this.modelBuilder = modelBuilder;
}
public async Task> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, HashSet languages)
{
List result = null;
- await ForSchemaAsync(schemaId, async (collection, schema) =>
+ await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent cursor;
try
{
- var parser = schema.ParseQuery(languages, odataQuery);
+ var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
+
+ var parser = model.ParseQuery(odataQuery);
- cursor = collection.Find(parser, schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schema);
+ cursor = collection.Find(parser, schemaEntity.Schema, nonPublished).Take(parser).Skip(parser).Sort(parser, schemaEntity.Schema);
}
catch (NotSupportedException)
{
@@ -76,7 +81,7 @@ namespace Squidex.Read.MongoDb.Contents
foreach (var entity in entities)
{
- entity.ParseData(schema);
+ entity.ParseData(schemaEntity.Schema);
}
result = entities.OfType().ToList();
@@ -89,14 +94,16 @@ namespace Squidex.Read.MongoDb.Contents
{
var result = 0L;
- await ForSchemaAsync(schemaId, async (collection, schema) =>
+ await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
IFindFluent cursor;
try
{
- var parser = schema.ParseQuery(languages, odataQuery);
+ var model = modelBuilder.BuildEdmModel(schemaEntity, languages);
+
+ var parser = model.ParseQuery(odataQuery);
- cursor = collection.Find(parser, schema, nonPublished);
+ cursor = collection.Find(parser, schemaEntity.Schema, nonPublished);
}
catch (NotSupportedException)
{
@@ -121,28 +128,28 @@ namespace Squidex.Read.MongoDb.Contents
{
MongoContentEntity result = null;
- await ForSchemaAsync(schemaId, async (collection, schema) =>
+ await ForSchemaAsync(schemaId, async (collection, schemaEntity) =>
{
result = await collection.Find(x => x.Id == id).FirstOrDefaultAsync();
- result?.ParseData(schema);
+ result?.ParseData(schemaEntity.Schema);
});
return result;
}
- private async Task ForSchemaAsync(Guid schemaId, Func, Schema, Task> action)
+ private async Task ForSchemaAsync(Guid schemaId, Func, ISchemaEntityWithSchema, Task> action)
{
var collection = GetCollection(schemaId);
- var schemaEntity = await schemaProvider.FindSchemaByIdAsync(schemaId);
+ var schemaEntity = await schemas.FindSchemaByIdAsync(schemaId, true);
if (schemaEntity == null)
{
return;
}
- await action(collection, schemaEntity.Schema);
+ await action(collection, schemaEntity);
}
}
}
diff --git a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
index 8574dd475..d2abc0e59 100644
--- a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
+++ b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
@@ -12,7 +12,6 @@ using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Events.Contents;
using Squidex.Events.Schemas;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
@@ -68,24 +67,24 @@ namespace Squidex.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{
- return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
+ return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.CreateAsync(@event, headers, x =>
{
SimpleMapper.Map(@event, x);
- x.SetData(schema, @event.Data);
+ x.SetData(schemaEntity.Schema, @event.Data);
});
});
}
protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{
- return ForSchemaAsync(@event.SchemaId.Id, (collection, schema) =>
+ return ForSchemaAsync(@event.SchemaId.Id, (collection, schemaEntity) =>
{
return collection.UpdateAsync(@event, headers, x =>
{
- x.SetData(schema, @event.Data);
+ x.SetData(schemaEntity.Schema, @event.Data);
});
});
}
@@ -112,19 +111,19 @@ namespace Squidex.Read.MongoDb.Contents
});
}
- protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
+ protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
{
- return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition($"Data.{@event.FieldId}")));
+ return collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
});
}
- protected Task On(ContentDeleted @event, EnvelopeHeaders headers)
+ protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
{
return ForSchemaIdAsync(@event.SchemaId.Id, collection =>
{
- return collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
+ return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition($"Data.{@event.FieldId}")));
});
}
diff --git a/src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs b/src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs
new file mode 100644
index 000000000..d3b992635
--- /dev/null
+++ b/src/Squidex.Read.MongoDb/Contents/Visitors/EdmModelExtensions.cs
@@ -0,0 +1,27 @@
+// ==========================================================================
+// SchemaExtensions.cs
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex Group
+// All rights reserved.
+// ==========================================================================
+
+using System;
+using System.Linq;
+using Microsoft.OData.Core.UriParser;
+using Microsoft.OData.Edm;
+
+namespace Squidex.Read.MongoDb.Contents.Visitors
+{
+ public static class EdmModelExtensions
+ {
+ public static ODataUriParser ParseQuery(this IEdmModel model, string query)
+ {
+ var path = model.EntityContainer.EntitySets().First().Path.Path.Last().Split('.').Last();
+
+ var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative));
+
+ return parser;
+ }
+ }
+}
diff --git a/src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs b/src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs
index b3e7dec12..7a2ff5a2f 100644
--- a/src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs
+++ b/src/Squidex.Read.MongoDb/History/MongoHistoryEventEntity.cs
@@ -14,7 +14,7 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Read.MongoDb.History
{
- public sealed class MongoHistoryEventEntity : MongoEntity, IAppRefEntity, ITrackCreatedByEntity
+ public sealed class MongoHistoryEventEntity : MongoEntity, IAppRefEntity, IEntityWithCreatedBy
{
[BsonRequired]
[BsonElement]
@@ -40,7 +40,7 @@ namespace Squidex.Read.MongoDb.History
[BsonElement]
public Dictionary Parameters { get; set; }
- RefToken ITrackCreatedByEntity.CreatedBy
+ RefToken IEntityWithCreatedBy.CreatedBy
{
get
{
diff --git a/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs b/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
index 0b57267b1..874764150 100644
--- a/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
+++ b/src/Squidex.Read.MongoDb/History/MongoHistoryEventRepository.cs
@@ -13,7 +13,6 @@ using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.History;
diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs
index ddbced902..8e34fb03c 100644
--- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs
+++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaEntity.cs
@@ -33,6 +33,10 @@ namespace Squidex.Read.MongoDb.Schemas
[BsonElement]
public string Schema { get; set; }
+ [BsonRequired]
+ [BsonElement]
+ public long Version { get; set; }
+
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
@@ -49,6 +53,10 @@ namespace Squidex.Read.MongoDb.Schemas
[BsonElement]
public bool IsPublished { get; set; }
+ [BsonRequired]
+ [BsonElement]
+ public bool IsDeleted { get; set; }
+
Schema ISchemaEntityWithSchema.Schema
{
get { return schema.Value; }
diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
index 58db8d1dc..c3767a308 100644
--- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
+++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository.cs
@@ -49,14 +49,14 @@ namespace Squidex.Read.MongoDb.Schemas
public async Task> QueryAllAsync(Guid appId)
{
- var entities = await Collection.Find(s => s.AppId == appId).ToListAsync();
+ var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync();
return entities.OfType().ToList();
}
public async Task> QueryAllWithSchemaAsync(Guid appId)
{
- var entities = await Collection.Find(s => s.AppId == appId).ToListAsync();
+ var entities = await Collection.Find(s => s.AppId == appId && !s.IsDeleted).ToListAsync();
entities.ForEach(x => x.DeserializeSchema(serializer));
@@ -66,7 +66,7 @@ namespace Squidex.Read.MongoDb.Schemas
public async Task FindSchemaAsync(Guid appId, string name)
{
var entity =
- await Collection.Find(s => s.Name == name && s.AppId == appId)
+ await Collection.Find(s => s.Name == name && s.AppId == appId && !s.IsDeleted)
.FirstOrDefaultAsync();
entity?.DeserializeSchema(serializer);
@@ -84,14 +84,5 @@ namespace Squidex.Read.MongoDb.Schemas
return entity;
}
-
- public async Task FindSchemaIdAsync(Guid appId, string name)
- {
- var entity =
- await Collection.Find(s => s.Name == name & s.AppId == appId)
- .Project(Projection.Include(x => x.Id)).FirstOrDefaultAsync();
-
- return entity?.Id;
- }
}
}
diff --git a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
index c72f76f9c..f0a94b350 100644
--- a/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
+++ b/src/Squidex.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
@@ -14,7 +14,6 @@ using Squidex.Events;
using Squidex.Events.Schemas;
using Squidex.Events.Schemas.Utils;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
@@ -92,7 +91,7 @@ namespace Squidex.Read.MongoDb.Schemas
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
- await Collection.DeleteOneAsync(x => x.Id == headers.AggregateId());
+ await Collection.UpdateAsync(@event, headers, e => e.IsDeleted = true);
SchemaSaved?.Invoke(@event.AppId, @event.SchemaId);
}
diff --git a/src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj b/src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj
index ab4e5369b..b6da4c68e 100644
--- a/src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj
+++ b/src/Squidex.Read.MongoDb/Squidex.Read.MongoDb.csproj
@@ -15,10 +15,10 @@
-
-
+
+
-
+
diff --git a/src/Squidex.Read.MongoDb/Utils/EntityMapper.cs b/src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
index 0e628b70e..f9b6c005d 100644
--- a/src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
+++ b/src/Squidex.Read.MongoDb/Utils/EntityMapper.cs
@@ -7,7 +7,7 @@
// ==========================================================================
using Squidex.Events;
-using Squidex.Infrastructure.CQRS;
+using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
@@ -23,6 +23,7 @@ namespace Squidex.Read.MongoDb.Utils
SetId(headers, entity);
+ SetVersion(headers, entity);
SetCreated(headers, entity);
SetCreatedBy(@event, entity);
@@ -33,6 +34,7 @@ namespace Squidex.Read.MongoDb.Utils
public static T Update(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : MongoEntity, new()
{
+ SetVersion(headers, entity);
SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity);
@@ -54,32 +56,33 @@ namespace Squidex.Read.MongoDb.Utils
entity.LastModified = headers.Timestamp();
}
- private static void SetCreatedBy(SquidexEvent @event, MongoEntity entity)
+ private static void SetVersion(EnvelopeHeaders headers, MongoEntity entity)
{
- var createdBy = entity as ITrackCreatedByEntity;
+ if (entity is IEntityWithVersion withVersion)
+ {
+ withVersion.Version = headers.EventStreamNumber();
+ }
+ }
- if (createdBy != null)
+ private static void SetCreatedBy(SquidexEvent @event, MongoEntity entity)
+ {
+ if (entity is IEntityWithCreatedBy withCreatedBy)
{
- createdBy.CreatedBy = @event.Actor;
+ withCreatedBy.CreatedBy = @event.Actor;
}
}
private static void SetLastModifiedBy(SquidexEvent @event, MongoEntity entity)
{
- var modifiedBy = entity as ITrackLastModifiedByEntity;
-
- if (modifiedBy != null)
+ if (entity is IEntityWithLastModifiedBy withModifiedBy)
{
- modifiedBy.LastModifiedBy = @event.Actor;
+ withModifiedBy.LastModifiedBy = @event.Actor;
}
}
private static void SetAppId(SquidexEvent @event, MongoEntity entity)
{
- var appEntity = entity as IAppRefEntity;
- var appEvent = @event as AppEvent;
-
- if (appEntity != null && appEvent != null)
+ if (entity is IAppRefEntity appEntity && @event is AppEvent appEvent)
{
appEntity.AppId = appEvent.AppId.Id;
}
diff --git a/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs b/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs
index a3fee940e..4bdd27df8 100644
--- a/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs
+++ b/src/Squidex.Read.MongoDb/Utils/MongoCollectionExtensions.cs
@@ -11,7 +11,7 @@ using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
+using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Read.MongoDb.Utils
diff --git a/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs b/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs
index a6f0360dc..e561c70e2 100644
--- a/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs
+++ b/src/Squidex.Read/Apps/AppHistoryEventsCreator.cs
@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Squidex.Events.Apps;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Read.History;
diff --git a/src/Squidex.Read/Apps/IAppEntity.cs b/src/Squidex.Read/Apps/IAppEntity.cs
index 79b7aa401..455aa00dd 100644
--- a/src/Squidex.Read/Apps/IAppEntity.cs
+++ b/src/Squidex.Read/Apps/IAppEntity.cs
@@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Read.Apps
{
- public interface IAppEntity : IEntity
+ public interface IAppEntity : IEntity, IEntityWithVersion
{
string Name { get; }
diff --git a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
index 69c3f1493..3537e873b 100644
--- a/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
+++ b/src/Squidex.Read/Apps/Services/Implementations/CachingAppProvider.cs
@@ -18,7 +18,7 @@ using Squidex.Read.Utils;
namespace Squidex.Read.Apps.Services.Implementations
{
- public class CachingAppProvider : CachingProvider, IAppProvider
+ public class CachingAppProvider : CachingProviderBase, IAppProvider
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30);
private readonly IAppRepository repository;
diff --git a/src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs b/src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs
similarity index 57%
rename from src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs
rename to src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs
index 7a9cf44ca..3acb4e287 100644
--- a/src/Squidex.Read.MongoDb/Contents/Visitors/SchemaExtensions.cs
+++ b/src/Squidex.Read/Contents/Builders/EdmModelBuilder.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// SchemaExtensions.cs
+// EdmModelBuilder.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@@ -8,19 +8,45 @@
using System;
using System.Collections.Generic;
-using Microsoft.OData.Core.UriParser;
+using System.Linq;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Squidex.Core.Schemas;
using Squidex.Infrastructure;
+using Squidex.Read.Schemas;
+using Squidex.Read.Utils;
-namespace Squidex.Read.MongoDb.Contents.Visitors
+namespace Squidex.Read.Contents.Builders
{
- public static class SchemaExtensions
+ public sealed class EdmModelBuilder : CachingProviderBase
{
- public static EdmModel BuildEdmModel(this Schema schema, HashSet languages)
+ public EdmModelBuilder(IMemoryCache cache)
+ : base(cache)
+ {
+ }
+
+ public IEdmModel BuildEdmModel(ISchemaEntityWithSchema schemaEntity, HashSet languages)
+ {
+ Guard.NotNull(languages, nameof(languages));
+ Guard.NotNull(schemaEntity, nameof(schemaEntity));
+
+ var cacheKey = $"{schemaEntity.Id}_{schemaEntity.Version}_{string.Join(",", languages.Select(x => x.Iso2Code).OrderBy(x => x))}";
+
+ var result = Cache.GetOrCreate(cacheKey, entry =>
+ {
+ entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60);
+
+ return BuildEdmModel(schemaEntity.Schema, languages);
+ });
+
+ return result;
+ }
+
+ private static EdmModel BuildEdmModel(Schema schema, HashSet languages)
{
var model = new EdmModel();
+
var container = new EdmEntityContainer("Squidex", "Container");
var schemaType = schema.BuildEdmType(languages, x =>
@@ -41,18 +67,9 @@ namespace Squidex.Read.MongoDb.Contents.Visitors
model.AddElement(schemaType);
model.AddElement(entityType);
- container.AddEntitySet($"{schema.Name}_Set", entityType);
+ container.AddEntitySet("ContentSet", entityType);
return model;
}
-
- public static ODataUriParser ParseQuery(this Schema schema, HashSet languages, string query)
- {
- var model = schema.BuildEdmModel(languages);
-
- var parser = new ODataUriParser(model, new Uri($"{schema.Name}_Set?{query}", UriKind.Relative));
-
- return parser;
- }
}
}
diff --git a/src/Squidex.Read/Contents/ContentHistoryEventsCreator.cs b/src/Squidex.Read/Contents/ContentHistoryEventsCreator.cs
index 29e9aa32d..3e62195a3 100644
--- a/src/Squidex.Read/Contents/ContentHistoryEventsCreator.cs
+++ b/src/Squidex.Read/Contents/ContentHistoryEventsCreator.cs
@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Squidex.Events.Contents;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.History;
diff --git a/src/Squidex.Read/Contents/IContentEntity.cs b/src/Squidex.Read/Contents/IContentEntity.cs
index 78460b80e..f672b129c 100644
--- a/src/Squidex.Read/Contents/IContentEntity.cs
+++ b/src/Squidex.Read/Contents/IContentEntity.cs
@@ -10,7 +10,7 @@ using Squidex.Core.Contents;
namespace Squidex.Read.Contents
{
- public interface IContentEntity : IAppRefEntity, ITrackCreatedByEntity, ITrackLastModifiedByEntity
+ public interface IContentEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{
bool IsPublished { get; }
diff --git a/src/Squidex.Read/History/HistoryEventsCreatorBase.cs b/src/Squidex.Read/History/HistoryEventsCreatorBase.cs
index 824a39140..c41c8a7b3 100644
--- a/src/Squidex.Read/History/HistoryEventsCreatorBase.cs
+++ b/src/Squidex.Read/History/HistoryEventsCreatorBase.cs
@@ -9,7 +9,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
// ReSharper disable ConvertIfStatementToReturnStatement
diff --git a/src/Squidex.Read/History/IHistoryEventsCreator.cs b/src/Squidex.Read/History/IHistoryEventsCreator.cs
index bae86c240..6fafe77cb 100644
--- a/src/Squidex.Read/History/IHistoryEventsCreator.cs
+++ b/src/Squidex.Read/History/IHistoryEventsCreator.cs
@@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Read.History
diff --git a/src/Squidex.Read/ITrackCreatedByEntity.cs b/src/Squidex.Read/IEntityWithCreatedBy.cs
similarity index 91%
rename from src/Squidex.Read/ITrackCreatedByEntity.cs
rename to src/Squidex.Read/IEntityWithCreatedBy.cs
index 6d79a3f7f..5aad9c2f9 100644
--- a/src/Squidex.Read/ITrackCreatedByEntity.cs
+++ b/src/Squidex.Read/IEntityWithCreatedBy.cs
@@ -10,7 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Read
{
- public interface ITrackCreatedByEntity
+ public interface IEntityWithCreatedBy
{
RefToken CreatedBy { get; set; }
}
diff --git a/src/Squidex.Read/ITrackLastModifiedByEntity.cs b/src/Squidex.Read/IEntityWithLastModifiedBy.cs
similarity index 90%
rename from src/Squidex.Read/ITrackLastModifiedByEntity.cs
rename to src/Squidex.Read/IEntityWithLastModifiedBy.cs
index d19b36266..11976ab9a 100644
--- a/src/Squidex.Read/ITrackLastModifiedByEntity.cs
+++ b/src/Squidex.Read/IEntityWithLastModifiedBy.cs
@@ -10,7 +10,7 @@ using Squidex.Infrastructure;
namespace Squidex.Read
{
- public interface ITrackLastModifiedByEntity
+ public interface IEntityWithLastModifiedBy
{
RefToken LastModifiedBy { get; set; }
}
diff --git a/src/Squidex.Infrastructure/CQRS/Events/IEventProcessor.cs b/src/Squidex.Read/IEntityWithVersion.cs
similarity index 54%
rename from src/Squidex.Infrastructure/CQRS/Events/IEventProcessor.cs
rename to src/Squidex.Read/IEntityWithVersion.cs
index 9a94a51fe..195e53bf2 100644
--- a/src/Squidex.Infrastructure/CQRS/Events/IEventProcessor.cs
+++ b/src/Squidex.Read/IEntityWithVersion.cs
@@ -1,18 +1,15 @@
// ==========================================================================
-// IEventProcessor.cs
+// IEntityWithVersion.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
-using System.Threading.Tasks;
-using Squidex.Infrastructure.CQRS.Commands;
-
-namespace Squidex.Infrastructure.CQRS.Events
+namespace Squidex.Read
{
- public interface IEventProcessor
+ public interface IEntityWithVersion
{
- Task ProcessEventAsync(Envelope @event, IAggregate aggregate, ICommand command);
+ long Version { get; set; }
}
}
diff --git a/src/Squidex.Read/Schemas/ISchemaEntity.cs b/src/Squidex.Read/Schemas/ISchemaEntity.cs
index 33c5479ce..d8ae691da 100644
--- a/src/Squidex.Read/Schemas/ISchemaEntity.cs
+++ b/src/Squidex.Read/Schemas/ISchemaEntity.cs
@@ -8,12 +8,14 @@
namespace Squidex.Read.Schemas
{
- public interface ISchemaEntity : IAppRefEntity, ITrackCreatedByEntity, ITrackLastModifiedByEntity
+ public interface ISchemaEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{
string Name { get; }
string Label { get; }
bool IsPublished { get; }
+
+ bool IsDeleted { get; }
}
}
diff --git a/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs b/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs
index ddb4c743e..d540c343c 100644
--- a/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs
+++ b/src/Squidex.Read/Schemas/Repositories/ISchemaRepository.cs
@@ -24,7 +24,5 @@ namespace Squidex.Read.Schemas.Repositories
Task FindSchemaAsync(Guid appId, string name);
Task FindSchemaAsync(Guid schemaId);
-
- Task FindSchemaIdAsync(Guid appId, string name);
}
}
diff --git a/src/Squidex.Read/Schemas/SchemaHistoryEventsCreator.cs b/src/Squidex.Read/Schemas/SchemaHistoryEventsCreator.cs
index dcbb779b7..9c395f9a3 100644
--- a/src/Squidex.Read/Schemas/SchemaHistoryEventsCreator.cs
+++ b/src/Squidex.Read/Schemas/SchemaHistoryEventsCreator.cs
@@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Squidex.Events;
using Squidex.Events.Schemas;
using Squidex.Infrastructure;
-using Squidex.Infrastructure.CQRS;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Read.History;
@@ -65,25 +64,21 @@ namespace Squidex.Read.Schemas
protected override Task CreateEventCoreAsync(Envelope @event)
{
- var schemaEvent = @event.Payload as SchemaEvent;
-
- if (schemaEvent == null)
+ if (@event.Payload is SchemaEvent schemaEvent)
{
- return Task.FromResult(null);
- }
+ string channel = $"schemas.{schemaEvent.SchemaId.Name}";
- string channel = $"schemas.{schemaEvent.SchemaId.Name}";
+ var result = ForEvent(@event.Payload, channel).AddParameter("Name", schemaEvent.SchemaId.Name);
- var result = ForEvent(@event.Payload, channel).AddParameter("Name", schemaEvent.SchemaId.Name);
+ if (schemaEvent is FieldEvent fieldEvent)
+ {
+ result.AddParameter("Field", fieldEvent.FieldId.Name);
+ }
- var fieldEvent = schemaEvent as FieldEvent;
-
- if (fieldEvent != null)
- {
- result.AddParameter("Field", fieldEvent.FieldId.Name);
+ return Task.FromResult(result);
}
- return Task.FromResult(result);
+ return Task.FromResult(null);
}
}
}
\ No newline at end of file
diff --git a/src/Squidex.Read/Schemas/Services/ISchemaProvider.cs b/src/Squidex.Read/Schemas/Services/ISchemaProvider.cs
index 02dfcf03b..73b8a1df8 100644
--- a/src/Squidex.Read/Schemas/Services/ISchemaProvider.cs
+++ b/src/Squidex.Read/Schemas/Services/ISchemaProvider.cs
@@ -14,7 +14,7 @@ namespace Squidex.Read.Schemas.Services
{
public interface ISchemaProvider
{
- Task FindSchemaByIdAsync(Guid id);
+ Task FindSchemaByIdAsync(Guid id, bool provideDeleted = false);
Task FindSchemaByNameAsync(Guid appId, string name);
diff --git a/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs b/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
index 85cd97588..1d600f32f 100644
--- a/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
+++ b/src/Squidex.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs
@@ -19,7 +19,7 @@ using Squidex.Read.Utils;
namespace Squidex.Read.Schemas.Services.Implementations
{
- public class CachingSchemaProvider : CachingProvider, ISchemaProvider
+ public class CachingSchemaProvider : CachingProviderBase, ISchemaProvider
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private readonly ISchemaRepository repository;
@@ -32,7 +32,7 @@ namespace Squidex.Read.Schemas.Services.Implementations
this.repository = repository;
}
- public async Task FindSchemaByIdAsync(Guid id)
+ public async Task FindSchemaByIdAsync(Guid id, bool provideDeleted = false)
{
var cacheKey = BuildIdCacheKey(id);
@@ -48,6 +48,11 @@ namespace Squidex.Read.Schemas.Services.Implementations
}
}
+ if (result != null && result.IsDeleted && !provideDeleted)
+ {
+ result = null;
+ }
+
return result;
}
@@ -69,6 +74,11 @@ namespace Squidex.Read.Schemas.Services.Implementations
}
}
+ if (result != null && result.IsDeleted)
+ {
+ result = null;
+ }
+
return result;
}
diff --git a/src/Squidex.Read/Squidex.Read.csproj b/src/Squidex.Read/Squidex.Read.csproj
index 911f23a17..fca27e890 100644
--- a/src/Squidex.Read/Squidex.Read.csproj
+++ b/src/Squidex.Read/Squidex.Read.csproj
@@ -13,7 +13,12 @@
-
+
+
+
+ C:\Users\mail2\.nuget\packages\identityserver4\1.1.1\lib\netstandard1.4\IdentityServer4.dll
+
+
diff --git a/src/Squidex.Read/Utils/CachingProvider.cs b/src/Squidex.Read/Utils/CachingProviderBase.cs
similarity index 83%
rename from src/Squidex.Read/Utils/CachingProvider.cs
rename to src/Squidex.Read/Utils/CachingProviderBase.cs
index 84f12e4ff..00a3491c6 100644
--- a/src/Squidex.Read/Utils/CachingProvider.cs
+++ b/src/Squidex.Read/Utils/CachingProviderBase.cs
@@ -1,5 +1,5 @@
// ==========================================================================
-// CachingProvider.cs
+// CachingProviderBase.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
@@ -11,7 +11,7 @@ using Squidex.Infrastructure;
namespace Squidex.Read.Utils
{
- public abstract class CachingProvider
+ public abstract class CachingProviderBase
{
private readonly IMemoryCache cache;
@@ -20,7 +20,7 @@ namespace Squidex.Read.Utils
get { return cache; }
}
- protected CachingProvider(IMemoryCache cache)
+ protected CachingProviderBase(IMemoryCache cache)
{
Guard.NotNull(cache, nameof(cache));
diff --git a/src/Squidex.Write/Apps/AppCommandHandler.cs b/src/Squidex.Write/Apps/AppCommandHandler.cs
index d77de95f9..6657528e2 100644
--- a/src/Squidex.Write/Apps/AppCommandHandler.cs
+++ b/src/Squidex.Write/Apps/AppCommandHandler.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
+using Squidex.Infrastructure.Tasks;
using Squidex.Read.Apps.Repositories;
using Squidex.Read.Users.Repositories;
using Squidex.Write.Apps.Commands;
@@ -51,11 +52,11 @@ namespace Squidex.Write.Apps
throw new ValidationException("Cannot create a new app", error);
}
- await handler.CreateAsync(command, x =>
+ await handler.CreateAsync(context, a =>
{
- x.Create(command);
+ a.Create(command);
- context.Succeed(command.AggregateId);
+ context.Succeed(EntityCreatedResult.Create(a.Id, a.Version));
});
}
@@ -70,55 +71,52 @@ namespace Squidex.Write.Apps
throw new ValidationException("Cannot assign contributor to app", error);
}
- await handler.UpdateAsync(command, x =>
- {
- x.AssignContributor(command);
- });
+ await handler.UpdateAsync(context, a => a.AssignContributor(command));
}
protected Task On(AttachClient command, CommandContext context)
{
- return handler.UpdateAsync(command, x =>
+ return handler.UpdateAsync(context, a =>
{
- x.AttachClient(command, keyGenerator.GenerateKey());
+ a.AttachClient(command, keyGenerator.GenerateKey());
- context.Succeed(x.Clients[command.Id]);
+ context.Succeed(EntityCreatedResult.Create(a.Clients[command.Id], a.Version));
});
}
protected Task On(RemoveContributor command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.RemoveContributor(command));
+ return handler.UpdateAsync(context, a => a.RemoveContributor(command));
}
protected Task On(RenameClient command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.RenameClient(command));
+ return handler.UpdateAsync(context, a => a.RenameClient(command));
}
protected Task On(RevokeClient command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.RevokeClient(command));
+ return handler.UpdateAsync(context, a => a.RevokeClient(command));
}
protected Task On(AddLanguage command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.AddLanguage(command));
+ return handler.UpdateAsync(context, a => a.AddLanguage(command));
}
protected Task On(RemoveLanguage command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.RemoveLanguage(command));
+ return handler.UpdateAsync(context, a => a.RemoveLanguage(command));
}
protected Task On(SetMasterLanguage command, CommandContext context)
{
- return handler.UpdateAsync(command, x => x.SetMasterLanguage(command));
+ return handler.UpdateAsync(context, a => a.SetMasterLanguage(command));
}
public Task HandleAsync(CommandContext context)
{
- return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);
+ return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
}
}
}
diff --git a/src/Squidex.Write/Apps/AppDomainObject.cs b/src/Squidex.Write/Apps/AppDomainObject.cs
index c2ee22d62..b1d8a1aae 100644
--- a/src/Squidex.Write/Apps/AppDomainObject.cs
+++ b/src/Squidex.Write/Apps/AppDomainObject.cs
@@ -22,7 +22,7 @@ using Squidex.Write.Apps.Commands;
namespace Squidex.Write.Apps
{
- public class AppDomainObject : DomainObject
+ public class AppDomainObject : DomainObjectBase
{
private static readonly Language DefaultLanguage = Language.EN;
private readonly AppContributors contributors = new AppContributors();
diff --git a/src/Squidex.Write/Apps/ClientKeyGenerator.cs b/src/Squidex.Write/Apps/ClientKeyGenerator.cs
index 965dc676e..a316b9061 100644
--- a/src/Squidex.Write/Apps/ClientKeyGenerator.cs
+++ b/src/Squidex.Write/Apps/ClientKeyGenerator.cs
@@ -16,14 +16,21 @@ namespace Squidex.Write.Apps
{
public class ClientKeyGenerator
{
+ private readonly Func algorithmFactory;
+
+ public ClientKeyGenerator()
+ {
+ algorithmFactory = SHA256.Create;
+ }
+
public virtual string GenerateKey()
{
- return Sha256(Guid.NewGuid().ToString());
+ return Hash(Guid.NewGuid().ToString());
}
- private static string Sha256(string input)
+ private string Hash(string input)
{
- using (var sha = SHA256.Create())
+ using (var sha = algorithmFactory())
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = sha.ComputeHash(bytes);
diff --git a/src/Squidex.Write/Contents/ContentCommandHandler.cs b/src/Squidex.Write/Contents/ContentCommandHandler.cs
index e63785ca8..8b6e74739 100644
--- a/src/Squidex.Write/Contents/ContentCommandHandler.cs
+++ b/src/Squidex.Write/Contents/ContentCommandHandler.cs
@@ -9,9 +9,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
+using Squidex.Core;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
+using Squidex.Infrastructure.Tasks;
using Squidex.Read.Apps.Services;
using Squidex.Read.Schemas.Services;
using Squidex.Write.Contents.Commands;
@@ -22,31 +24,32 @@ namespace Squidex.Write.Contents
{
private readonly IAggregateHandler handler;
private readonly IAppProvider appProvider;
- private readonly ISchemaProvider schemaProvider;
+ private readonly ISchemaProvider schemas;
public ContentCommandHandler(
IAggregateHandler handler,
IAppProvider appProvider,
- ISchemaProvider schemaProvider)
+ ISchemaProvider schemas)
{
Guard.NotNull(handler, nameof(handler));
+ Guard.NotNull(schemas, nameof(schemas));
Guard.NotNull(appProvider, nameof(appProvider));
- Guard.NotNull(schemaProvider, nameof(schemaProvider));
this.handler = handler;
+ this.schemas = schemas;
+
this.appProvider = appProvider;
- this.schemaProvider = schemaProvider;
}
protected async Task On(CreateContent command, CommandContext context)
{
- await ValidateAsync(command, () => "Failed to create content");
+ await ValidateAsync(command, () => "Failed to create content", true);
- await handler.CreateAsync(command, s =>
+ await handler.CreateAsync(context, c =>
{
- s.Create(command);
+ c.Create(command);
- context.Succeed(command.ContentId);
+ context.Succeed(EntityCreatedResult.Create(command.Data, c.Version));
});
}
@@ -54,45 +57,43 @@ namespace Squidex.Write.Contents
{
await ValidateAsync(command, () => "Failed to update content");
- await handler.UpdateAsync(command, s => s.Update(command));
+ await handler.UpdateAsync(context, c => c.Update(command));
}
protected async Task On(PatchContent command, CommandContext context)
{
await ValidateAsync(command, () => "Failed to patch content");
- await handler.UpdateAsync(command, s => s.Patch(command));
+ await handler.UpdateAsync(context, c => c.Patch(command));
}
protected Task On(PublishContent command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.Publish(command));
+ return handler.UpdateAsync(context, c => c.Publish(command));
}
protected Task On(UnpublishContent command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.Unpublish(command));
+ return handler.UpdateAsync(context, c => c.Unpublish(command));
}
protected Task On(DeleteContent command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.Delete(command));
+ return handler.UpdateAsync(context, c => c.Delete(command));
}
public Task HandleAsync(CommandContext context)
{
- return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);
+ return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
}
- private async Task ValidateAsync(ContentDataCommand command, Func message)
+ private async Task ValidateAsync(ContentDataCommand command, Func message, bool enrich = false)
{
Guard.Valid(command, nameof(command), message);
- var taskForApp =
- appProvider.FindAppByIdAsync(command.AppId.Id);
+ var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id);
- var taskForSchema =
- schemaProvider.FindSchemaByIdAsync(command.SchemaId.Id);
+ var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id);
await Task.WhenAll(taskForApp, taskForSchema);
@@ -101,14 +102,17 @@ namespace Squidex.Write.Contents
var schemaObject = taskForSchema.Result.Schema;
var schemaErrors = new List();
- await schemaObject.ValidateAsync(command.Data, schemaErrors, languages);
-
- schemaObject.Enrich(command.Data, languages);
+ await command.Data.ValidateAsync(schemaObject, languages, schemaErrors);
if (schemaErrors.Count > 0)
{
throw new ValidationException(message(), schemaErrors);
}
+
+ if (enrich)
+ {
+ command.Data.Enrich(schemaObject, languages);
+ }
}
}
}
diff --git a/src/Squidex.Write/Contents/ContentDomainObject.cs b/src/Squidex.Write/Contents/ContentDomainObject.cs
index 269fef8ee..f56eda13e 100644
--- a/src/Squidex.Write/Contents/ContentDomainObject.cs
+++ b/src/Squidex.Write/Contents/ContentDomainObject.cs
@@ -18,7 +18,7 @@ using Squidex.Write.Contents.Commands;
namespace Squidex.Write.Contents
{
- public class ContentDomainObject : DomainObject
+ public class ContentDomainObject : DomainObjectBase
{
private bool isDeleted;
private bool isCreated;
diff --git a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs
index 2e8af2d13..d4af27a0f 100644
--- a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs
+++ b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Dispatching;
+using Squidex.Infrastructure.Tasks;
using Squidex.Read.Schemas.Services;
using Squidex.Write.Schemas.Commands;
@@ -41,77 +42,77 @@ namespace Squidex.Write.Schemas
throw new ValidationException("Cannot create a new schema", error);
}
- await handler.CreateAsync(command, s =>
+ await handler.CreateAsync(context, s =>
{
s.Create(command);
- context.Succeed(command.Name);
+ context.Succeed(EntityCreatedResult.Create(s.Id, s.Version));
});
}
protected Task On(AddField command, CommandContext context)
{
- return handler.UpdateAsync(command, s =>
+ return handler.UpdateAsync(context, s =>
{
s.AddField(command);
- context.Succeed(s.Schema.Fields.Values.First(x => x.Name == command.Name).Id);
+ context.Succeed(EntityCreatedResult.Create(s.Schema.Fields.Values.First(x => x.Name == command.Name).Id, s.Version));
});
}
protected Task On(DeleteSchema command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.Delete(command));
+ return handler.UpdateAsync(context, s => s.Delete(command));
}
protected Task On(DeleteField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.DeleteField(command));
+ return handler.UpdateAsync(context, s => s.DeleteField(command));
}
protected Task On(DisableField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.DisableField(command));
+ return handler.UpdateAsync(context, s => s.DisableField(command));
}
protected Task On(EnableField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.EnableField(command));
+ return handler.UpdateAsync(context, s => s.EnableField(command));
}
protected Task On(HideField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.HideField(command));
+ return handler.UpdateAsync(context, s => s.HideField(command));
}
protected Task On(ShowField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.ShowField(command));
+ return handler.UpdateAsync(context, s => s.ShowField(command));
}
protected Task On(UpdateSchema command, CommandContext context)
{
- return handler.UpdateAsync(command, s => s.Update(command));
+ return handler.UpdateAsync(context, s => s.Update(command));
}
protected Task On(UpdateField command, CommandContext context)
{
- return handler.UpdateAsync(command, s => { s.UpdateField(command); });
+ return handler.UpdateAsync(context, s => s.UpdateField(command));
}
protected Task On(PublishSchema command, CommandContext context)
{
- return handler.UpdateAsync(command, s => { s.Publish(command); });
+ return handler.UpdateAsync(context, s => s.Publish(command));
}
protected Task On(UnpublishSchema command, CommandContext context)
{
- return handler.UpdateAsync(command, s => { s.Unpublish(command); });
+ return handler.UpdateAsync(context, s => s.Unpublish(command));
}
public Task HandleAsync(CommandContext context)
{
- return context.IsHandled ? Task.FromResult(false) : this.DispatchActionAsync(context.Command, context);
+ return context.IsHandled ? TaskHelper.False : this.DispatchActionAsync(context.Command, context);
}
}
}
diff --git a/src/Squidex.Write/Schemas/SchemaDomainObject.cs b/src/Squidex.Write/Schemas/SchemaDomainObject.cs
index edaf8d9f3..bda7fdab9 100644
--- a/src/Squidex.Write/Schemas/SchemaDomainObject.cs
+++ b/src/Squidex.Write/Schemas/SchemaDomainObject.cs
@@ -19,7 +19,7 @@ using Squidex.Write.Schemas.Commands;
namespace Squidex.Write.Schemas
{
- public class SchemaDomainObject : DomainObject
+ public class SchemaDomainObject : DomainObjectBase
{
private readonly FieldRegistry registry;
private bool isDeleted;
diff --git a/src/Squidex.Write/SquidexCommand.cs b/src/Squidex.Write/SquidexCommand.cs
index ae3637166..aed4baa52 100644
--- a/src/Squidex.Write/SquidexCommand.cs
+++ b/src/Squidex.Write/SquidexCommand.cs
@@ -14,5 +14,7 @@ namespace Squidex.Write
public abstract class SquidexCommand : ICommand
{
public RefToken Actor { get; set; }
+
+ public long? ExpectedVersion { get; set; }
}
}
diff --git a/src/Squidex/Config/Constants.cs b/src/Squidex/Config/Constants.cs
index 0c994fb2a..f1a9f9385 100644
--- a/src/Squidex/Config/Constants.cs
+++ b/src/Squidex/Config/Constants.cs
@@ -8,8 +8,10 @@
namespace Squidex.Config
{
- public class Constants
+ public static class Constants
{
+ public static readonly string SecurityDefinition = "oauth-client-auth";
+
public static readonly string ApiPrefix = "/api";
public static readonly string ApiScope = "squidex-api";
diff --git a/src/Squidex/Config/Domain/ClusterModule.cs b/src/Squidex/Config/Domain/ClusterModule.cs
index 6087526a4..1e220f1d5 100644
--- a/src/Squidex/Config/Domain/ClusterModule.cs
+++ b/src/Squidex/Config/Domain/ClusterModule.cs
@@ -18,7 +18,7 @@ namespace Squidex.Config.Domain
{
public class ClusterModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public ClusterModule(IConfiguration configuration)
{
diff --git a/src/Squidex/Config/Domain/EventStoreModule.cs b/src/Squidex/Config/Domain/EventStoreModule.cs
index 9e6f0f4fd..ccc10e035 100644
--- a/src/Squidex/Config/Domain/EventStoreModule.cs
+++ b/src/Squidex/Config/Domain/EventStoreModule.cs
@@ -19,7 +19,7 @@ namespace Squidex.Config.Domain
{
public class EventStoreModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public EventStoreModule(IConfiguration configuration)
{
diff --git a/src/Squidex/Config/Domain/InfrastructureModule.cs b/src/Squidex/Config/Domain/InfrastructureModule.cs
index b430e5f15..68e3851af 100644
--- a/src/Squidex/Config/Domain/InfrastructureModule.cs
+++ b/src/Squidex/Config/Domain/InfrastructureModule.cs
@@ -20,11 +20,13 @@ using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.CQRS.Events;
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
namespace Squidex.Config.Domain
{
public class InfrastructureModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public InfrastructureModule(IConfiguration configuration)
{
diff --git a/src/Squidex/Config/Domain/ReadModule.cs b/src/Squidex/Config/Domain/ReadModule.cs
index 980b63975..eec94620a 100644
--- a/src/Squidex/Config/Domain/ReadModule.cs
+++ b/src/Squidex/Config/Domain/ReadModule.cs
@@ -12,16 +12,19 @@ using Squidex.Read.Apps;
using Squidex.Read.Apps.Services;
using Squidex.Read.Apps.Services.Implementations;
using Squidex.Read.Contents;
+using Squidex.Read.Contents.Builders;
using Squidex.Read.History;
using Squidex.Read.Schemas;
using Squidex.Read.Schemas.Services;
using Squidex.Read.Schemas.Services.Implementations;
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
namespace Squidex.Config.Domain
{
public sealed class ReadModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public ReadModule(IConfiguration configuration)
{
@@ -49,6 +52,10 @@ namespace Squidex.Config.Domain
builder.RegisterType()
.As()
.SingleInstance();
+
+ builder.RegisterType()
+ .AsSelf()
+ .SingleInstance();
}
}
}
diff --git a/src/Squidex/Config/Domain/Serializers.cs b/src/Squidex/Config/Domain/Serializers.cs
index 3f23d2323..29c3b5733 100644
--- a/src/Squidex/Config/Domain/Serializers.cs
+++ b/src/Squidex/Config/Domain/Serializers.cs
@@ -10,7 +10,6 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
-using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using Squidex.Events;
@@ -21,22 +20,21 @@ namespace Squidex.Config.Domain
{
public static class Serializers
{
- private static readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry();
+ private static readonly TypeNameRegistry TypeNameRegistry = new TypeNameRegistry();
private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
{
- settings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry);
-
- settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
-
- settings.Converters.Add(new InstantConverter());
- settings.Converters.Add(new LanguageConverter());
- settings.Converters.Add(new NamedGuidIdConverter());
- settings.Converters.Add(new NamedLongIdConverter());
- settings.Converters.Add(new NamedStringIdConverter());
- settings.Converters.Add(new PropertiesBagConverter());
- settings.Converters.Add(new RefTokenConverter());
- settings.Converters.Add(new StringEnumConverter());
+ settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry);
+
+ settings.ContractResolver = new ConverterContractResolver(
+ new InstantConverter(),
+ new LanguageConverter(),
+ new NamedGuidIdConverter(),
+ new NamedLongIdConverter(),
+ new NamedStringIdConverter(),
+ new PropertiesBagConverter(),
+ new RefTokenConverter(),
+ new StringEnumConverter());
settings.NullValueHandling = NullValueHandling.Ignore;
@@ -52,7 +50,7 @@ namespace Squidex.Config.Domain
static Serializers()
{
- typeNameRegistry.Map(typeof(SquidexEvent).GetTypeInfo().Assembly);
+ TypeNameRegistry.Map(typeof(SquidexEvent).GetTypeInfo().Assembly);
}
private static JsonSerializerSettings CreateSettings()
@@ -67,7 +65,7 @@ namespace Squidex.Config.Domain
public static IServiceCollection AddMyEventFormatter(this IServiceCollection services)
{
- services.AddSingleton(t => typeNameRegistry);
+ services.AddSingleton(t => TypeNameRegistry);
services.AddSingleton(t => CreateSettings());
services.AddSingleton(t => CreateSerializer(t.GetRequiredService()));
diff --git a/src/Squidex/Config/Domain/StoreModule.cs b/src/Squidex/Config/Domain/StoreModule.cs
index 6da92ede5..194aff43a 100644
--- a/src/Squidex/Config/Domain/StoreModule.cs
+++ b/src/Squidex/Config/Domain/StoreModule.cs
@@ -15,7 +15,7 @@ namespace Squidex.Config.Domain
{
public class StoreModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public StoreModule(IConfiguration configuration)
{
diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs
index 4a152f644..5999a9215 100644
--- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs
+++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs
@@ -32,9 +32,10 @@ namespace Squidex.Config.Domain
{
public class StoreMongoDbModule : Module
{
- private const string MongoDatabaseName = "string";
+ private const string MongoDatabaseName = "MongoDatabaseName";
+ private const string MongoDatabaseNameContent = "MongoDatabaseNameContent";
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public StoreMongoDbModule(IConfiguration configuration)
{
@@ -57,13 +58,24 @@ namespace Squidex.Config.Domain
throw new ConfigurationException("You must specify the MongoDB connection string in the 'squidex:stores:mongoDb:connectionString' configuration section.");
}
- builder.Register(c =>
+ var databaseNameContent = Configuration.GetValue("squidex:stores:mongoDb:databaseNameContent");
+
+ if (string.IsNullOrWhiteSpace(databaseNameContent))
{
- var mongoDbClient = new MongoClient(connectionString);
- var mongoDatabase = mongoDbClient.GetDatabase(databaseName);
+ databaseNameContent = databaseName;
+ }
+
+ builder.Register(c => new MongoClient(connectionString))
+ .As()
+ .SingleInstance();
- return mongoDatabase;
- }).Named(MongoDatabaseName).SingleInstance();
+ builder.Register(c => c.Resolve().GetDatabase(databaseName))
+ .Named(MongoDatabaseName)
+ .SingleInstance();
+
+ builder.Register(c => c.Resolve().GetDatabase(databaseNameContent))
+ .Named(MongoDatabaseNameContent)
+ .SingleInstance();
builder.Register>(c =>
{
@@ -100,7 +112,7 @@ namespace Squidex.Config.Domain
.SingleInstance();
builder.RegisterType()
- .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseName))
+ .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseNameContent))
.As()
.As()
.AsSelf()
diff --git a/src/Squidex/Config/Domain/WriteModule.cs b/src/Squidex/Config/Domain/WriteModule.cs
index 767a48e86..66c169c90 100644
--- a/src/Squidex/Config/Domain/WriteModule.cs
+++ b/src/Squidex/Config/Domain/WriteModule.cs
@@ -10,17 +10,18 @@ using Autofac;
using Microsoft.Extensions.Configuration;
using Squidex.Core.Schemas;
using Squidex.Infrastructure.CQRS.Commands;
-using Squidex.Infrastructure.CQRS.Events;
using Squidex.Pipeline.CommandHandlers;
using Squidex.Write.Apps;
using Squidex.Write.Contents;
using Squidex.Write.Schemas;
+// ReSharper disable UnusedAutoPropertyAccessor.Local
+
namespace Squidex.Config.Domain
{
public class WriteModule : Module
{
- public IConfiguration Configuration { get; }
+ private IConfiguration Configuration { get; }
public WriteModule(IConfiguration configuration)
{
@@ -29,6 +30,10 @@ namespace Squidex.Config.Domain
protected override void Load(ContainerBuilder builder)
{
+ builder.RegisterType()
+ .As()
+ .SingleInstance();
+
builder.RegisterType()
.As()
.SingleInstance();
@@ -45,10 +50,6 @@ namespace Squidex.Config.Domain
.As()
.SingleInstance();
- builder.RegisterType()
- .As()
- .SingleInstance();
-
builder.RegisterType()
.AsSelf()
.SingleInstance();
@@ -69,11 +70,15 @@ namespace Squidex.Config.Domain
.As