From 61d82b042f748b647f965a5f19e12963660dcc3a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 26 Jan 2019 19:38:24 +0100 Subject: [PATCH 1/7] Schema sync. --- .../Schemas/ArrayField.cs | 5 + .../Schemas/Fields.cs | 2 +- .../Schemas/IArrayField.cs | 2 + .../Schemas/Schema.cs | 76 ++- .../SchemaSynchronizationOptions.cs | 16 + .../SchemaSynchronizer.cs | 208 ++++++++ .../EventSynchronization/SyncHelpers.cs | 46 ++ .../Schemas/SchemaScriptsConfigured.cs | 18 + .../EventSynchronization/AssertHelper.cs | 51 ++ .../SchemaSynchronizerTests.cs | 505 ++++++++++++++++++ 10 files changed, 920 insertions(+), 9 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizationOptions.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs create mode 100644 src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/AssertHelper.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs index 1de894b81..c58263cb1 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs @@ -31,6 +31,11 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields.ByName; } } + public FieldCollection FieldCollection + { + get { return fields; } + } + public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties = null, IFieldSettings settings = null) : base(id, name, partitioning, properties) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs index 570e75aa5..2ae68dabe 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Core.Schemas return new NestedField(id, name, properties, settings); } - public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func handler, ArrayFieldProperties properties = null, IFieldSettings settings = null) + public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func handler = null, ArrayFieldProperties properties = null, IFieldSettings settings = null) { var field = Array(id, name, partitioning, properties, settings); diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs index 0ea74cfbd..825013f46 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IArrayField.cs @@ -16,5 +16,7 @@ namespace Squidex.Domain.Apps.Core.Schemas IReadOnlyDictionary FieldsById { get; } IReadOnlyDictionary FieldsByName { get; } + + FieldCollection FieldCollection { get; } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs index c6e187087..717bec234 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs @@ -14,8 +14,14 @@ namespace Squidex.Domain.Apps.Core.Schemas { public sealed class Schema : Cloneable { + private static readonly Dictionary EmptyScripts = new Dictionary(); + private static readonly Dictionary EmptyPreviewUrls = new Dictionary(); private readonly string name; + private readonly bool isSingleton; + private string category; private FieldCollection fields = FieldCollection.Empty; + private IReadOnlyDictionary scripts = EmptyScripts; + private IReadOnlyDictionary previewUrls = EmptyPreviewUrls; private SchemaProperties properties; private bool isPublished; @@ -24,11 +30,21 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return name; } } + public string Category + { + get { return category; } + } + public bool IsPublished { get { return isPublished; } } + public bool IsSingleton + { + get { return isSingleton; } + } + public IReadOnlyList Fields { get { return fields.Ordered; } @@ -44,12 +60,27 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields.ByName; } } + public IReadOnlyDictionary Scripts + { + get { return scripts; } + } + + public IReadOnlyDictionary PreviewUrls + { + get { return previewUrls; } + } + + public FieldCollection FieldCollection + { + get { return fields; } + } + public SchemaProperties Properties { get { return properties; } } - public Schema(string name, SchemaProperties properties = null) + public Schema(string name, SchemaProperties properties = null, bool isSingleton = false) { Guard.NotNullOrEmpty(name, nameof(name)); @@ -57,10 +88,12 @@ namespace Squidex.Domain.Apps.Core.Schemas this.properties = properties ?? new SchemaProperties(); this.properties.Freeze(); + + this.isSingleton = isSingleton; } - public Schema(string name, RootField[] fields, SchemaProperties properties, bool isPublished) - : this(name, properties) + public Schema(string name, RootField[] fields, SchemaProperties properties, bool isPublished, bool isSingleton = false) + : this(name, properties, isSingleton) { Guard.NotNull(fields, nameof(fields)); @@ -99,31 +132,58 @@ namespace Squidex.Domain.Apps.Core.Schemas }); } + [Pure] + public Schema MoveTo(string category) + { + return Clone(clone => + { + clone.category = category; + }); + } + + [Pure] + public Schema ConfigureScripts(IReadOnlyDictionary scripts) + { + return Clone(clone => + { + clone.scripts = scripts ?? EmptyScripts; + }); + } + + [Pure] + public Schema ConfigurePreviewUrls(IReadOnlyDictionary previewUrls) + { + return Clone(clone => + { + clone.previewUrls = previewUrls ?? EmptyPreviewUrls; + }); + } + [Pure] public Schema DeleteField(long fieldId) { - return Updatefields(f => f.Remove(fieldId)); + return UpdateFields(f => f.Remove(fieldId)); } [Pure] public Schema ReorderFields(List ids) { - return Updatefields(f => f.Reorder(ids)); + return UpdateFields(f => f.Reorder(ids)); } [Pure] public Schema AddField(RootField field) { - return Updatefields(f => f.Add(field)); + return UpdateFields(f => f.Add(field)); } [Pure] public Schema UpdateField(long fieldId, Func updater) { - return Updatefields(f => f.Update(fieldId, updater)); + return UpdateFields(f => f.Update(fieldId, updater)); } - private Schema Updatefields(Func, FieldCollection> updater) + private Schema UpdateFields(Func, FieldCollection> updater) { var newFields = updater(fields); diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizationOptions.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizationOptions.cs new file mode 100644 index 000000000..35588d0b4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizationOptions.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.EventSynchronization +{ + public sealed class SchemaSynchronizationOptions + { + public bool NoFieldDeletion { get; set; } + + public bool NoFieldRecreation { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs new file mode 100644 index 000000000..5622b4807 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs @@ -0,0 +1,208 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; + +namespace Squidex.Domain.Apps.Core.EventSynchronization +{ + public static class SchemaSynchronizer + { + public static IEnumerable Synchronize(this Schema source, Schema target, IJsonSerializer serializer, Func idGenerator, SchemaSynchronizationOptions options = null) + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(serializer, nameof(serializer)); + Guard.NotNull(idGenerator, nameof(idGenerator)); + + if (target == null) + { + yield return new SchemaDeleted(); + } + else + { + options = options ?? new SchemaSynchronizationOptions(); + + SchemaEvent E(SchemaEvent @event) + { + return @event; + } + + var events = SyncFields(source.FieldCollection, target.FieldCollection, serializer, idGenerator, null, options); + + foreach (var @event in events) + { + yield return E(@event); + } + + if (!source.Properties.EqualsJson(target.Properties, serializer)) + { + yield return E(new SchemaUpdated { Properties = target.Properties }); + } + + if (!source.Category.StringEquals(target.Category)) + { + yield return E(new SchemaCategoryChanged { Name = target.Category }); + } + + if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls)) + { + yield return E(new SchemaPreviewUrlsConfigured { PreviewUrls = target.PreviewUrls.ToDictionary(x => x.Key, x => x.Value) }); + } + + if (!source.Scripts.EqualsDictionary(target.Scripts)) + { + yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts.ToDictionary(x => x.Key, x => x.Value) }); + } + + if (source.IsPublished != target.IsPublished) + { + yield return target.IsPublished ? + E(new SchemaPublished()) : + E(new SchemaUnpublished()); + } + } + } + + private static IEnumerable SyncFields( + FieldCollection source, + FieldCollection target, + IJsonSerializer serializer, + Func idGenerator, + NamedId parentId, SchemaSynchronizationOptions options) where T : class, IField + { + FieldEvent E(FieldEvent @event) + { + @event.ParentFieldId = parentId; + + return @event; + } + + var sourceIds = new List>(source.Ordered.Select(x => x.NamedId())); + var sourceNames = sourceIds.Select(x => x.Name).ToList(); + + if (!options.NoFieldDeletion) + { + foreach (var sourceField in source.Ordered) + { + if (!target.ByName.TryGetValue(sourceField.Name, out var targetField)) + { + var id = sourceField.NamedId(); + + sourceIds.Remove(id); + sourceNames.Remove(id.Name); + + yield return E(new FieldDeleted { FieldId = id }); + } + } + } + + foreach (var targetField in target.Ordered) + { + NamedId id = null; + + var canCreateField = true; + + if (source.ByName.TryGetValue(targetField.Name, out var sourceField)) + { + id = sourceField.NamedId(); + + if (CanUpdate(sourceField, targetField)) + { + if (!sourceField.RawProperties.EqualsJson(targetField.RawProperties, serializer)) + { + yield return E(new FieldUpdated { FieldId = id, Properties = targetField.RawProperties }); + } + } + else if (!sourceField.IsLocked && !options.NoFieldRecreation) + { + yield return E(new FieldDeleted { FieldId = id }); + } + else + { + canCreateField = false; + } + } + else if (canCreateField) + { + var partitioning = (string)null; + + if (targetField is IRootField rootField) + { + partitioning = rootField.Partitioning.Key; + } + + id = NamedId.Of(idGenerator(), targetField.Name); + + yield return new FieldAdded + { + Name = targetField.Name, + ParentFieldId = parentId, + Partitioning = partitioning, + Properties = targetField.RawProperties, + FieldId = id + }; + + sourceIds.Add(id); + sourceNames.Add(id.Name); + } + + if (id != null && (sourceField == null || CanUpdate(sourceField, targetField))) + { + if (!targetField.IsLocked.BoolEquals(sourceField?.IsLocked)) + { + yield return E(new FieldLocked { FieldId = id }); + } + + if (!targetField.IsHidden.BoolEquals(sourceField?.IsHidden)) + { + yield return targetField.IsHidden ? + E(new FieldHidden { FieldId = id }) : + E(new FieldShown { FieldId = id }); + } + + if (!targetField.IsDisabled.BoolEquals(sourceField?.IsDisabled)) + { + yield return targetField.IsDisabled ? + E(new FieldDisabled { FieldId = id }) : + E(new FieldEnabled { FieldId = id }); + } + + if ((sourceField == null || sourceField is IArrayField) && targetField is IArrayField targetArrayField) + { + var fields = (sourceField as IArrayField)?.FieldCollection ?? FieldCollection.Empty; + + var events = SyncFields(fields, targetArrayField.FieldCollection, serializer, idGenerator, id, options); + + foreach (var @event in events) + { + yield return @event; + } + } + } + } + + var targetNames = target.Ordered.Select(x => x.Name); + + if (sourceNames.Intersect(targetNames).Count() == target.Ordered.Count && !sourceNames.SequenceEqual(targetNames)) + { + yield return new SchemaFieldsReordered { FieldIds = sourceIds.Select(x => x.Id).ToList(), ParentFieldId = parentId }; + } + } + + private static bool CanUpdate(IField source, IField target) + { + return !source.IsLocked && source.Name == target.Name && source.RawProperties.TypeEquals(target.RawProperties); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs new file mode 100644 index 000000000..827ccb744 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; +using NamedIdStatic = Squidex.Infrastructure.NamedId; + +namespace Squidex.Domain.Apps.Core.EventSynchronization +{ + public static class SyncHelpers + { + public static bool BoolEquals(this bool lhs, bool? rhs) + { + return lhs == (rhs ?? false); + } + + public static bool StringEquals(this string lhs, string rhs) + { + return string.Equals(lhs ?? string.Empty, rhs ?? string.Empty, StringComparison.Ordinal); + } + + public static bool TypeEquals(this object lhs, object rhs) + { + return lhs.GetType() == rhs.GetType(); + } + + public static NamedId NamedId(this IField field) + { + return NamedIdStatic.Of(field.Id, field.Name); + } + + public static bool EqualsJson(this T lhs, T rhs, IJsonSerializer serializer) + { + var lhsJson = serializer.Serialize(lhs); + var rhsJson = serializer.Serialize(rhs); + + return string.Equals(lhsJson, rhsJson); + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs new file mode 100644 index 000000000..1deeabd55 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Schemas +{ + [EventType(nameof(SchemaScriptsConfigured))] + public sealed class SchemaScriptsConfigured : SchemaEvent + { + public Dictionary Scripts { get; set; } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/AssertHelper.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/AssertHelper.cs new file mode 100644 index 000000000..073a73677 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/AssertHelper.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using FluentAssertions.Equivalency; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Orleans; + +namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization +{ + public static class AssertHelper + { + public static void ShouldHaveSameEvents(this IEnumerable events, params IEvent[] others) + { + var source = events.ToArray(); + + source.Should().HaveSameCount(others); + + for (var i = 0; i < source.Length; i++) + { + var lhs = source[i]; + var rhs = others[i]; + + lhs.ShouldBeSameEvent(rhs); + } + } + + public static void ShouldBeSameEvent(this IEvent lhs, IEvent rhs) + { + lhs.Should().BeOfType(rhs.GetType()); + + ((object)lhs).Should().BeEquivalentTo(rhs, o => o.IncludingAllRuntimeProperties().Excluding((IMemberInfo x) => x.SelectedMemberPath == "Properties.IsFrozen")); + } + + public static void ShouldBeSameEventType(this IEvent lhs, IEvent rhs) + { + lhs.Should().BeOfType(rhs.GetType()); + } + + public static void ShouldBeEquivalent(this J lhs, T rhs) + { + lhs.Value.Should().BeEquivalentTo(rhs, o => o.IncludingProperties()); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs new file mode 100644 index 000000000..e6394ccb7 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs @@ -0,0 +1,505 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.EventSynchronization; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization +{ + public class SchemaSynchronizerTests + { + private readonly Func idGenerator; + private readonly IJsonSerializer jsonSerializer = TestUtils.DefaultSerializer; + private readonly NamedId stringId = NamedId.Of(13L, "my-value"); + private readonly NamedId nestedId = NamedId.Of(141L, "my-value"); + private readonly NamedId arrayId = NamedId.Of(14L, "11-array"); + private int fields = 50; + + public SchemaSynchronizerTests() + { + idGenerator = () => fields++; + } + + [Fact] + public void Should_create_events_if_schema_deleted() + { + var sourceSchema = new Schema("source"); + var targetSchema = (Schema)null; + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaDeleted() + ); + } + + [Fact] + public void Should_create_events_if_category_changed() + { + var sourceSchema = new Schema("source"); + var targetSchema = new Schema("target").MoveTo("Category"); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaCategoryChanged { Name = "Category" } + ); + } + + [Fact] + public void Should_create_events_if_scripts_configured() + { + var scripts = new Dictionary + { + ["Create"] = "Script" + }; + + var sourceSchema = new Schema("source"); + var targetSchema = new Schema("target").ConfigureScripts(scripts); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaScriptsConfigured { Scripts = scripts } + ); + } + + [Fact] + public void Should_create_events_if_preview_urls_configured() + { + var previewUrls = new Dictionary + { + ["Web"] = "Url" + }; + + var sourceSchema = new Schema("source"); + var targetSchema = new Schema("target").ConfigurePreviewUrls(previewUrls); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaPreviewUrlsConfigured { PreviewUrls = previewUrls } + ); + } + + [Fact] + public void Should_create_events_if_schema_published() + { + var sourceSchema = new Schema("source"); + var targetSchema = new Schema("target").Publish(); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaPublished() + ); + } + + [Fact] + public void Should_create_events_if_schema_unpublished() + { + var sourceSchema = new Schema("source").Publish(); + var targetSchema = new Schema("target"); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaUnpublished() + ); + } + + [Fact] + public void Should_create_events_if_nested_field_deleted() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldDeleted { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_deleted() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var targetSchema = + new Schema("target"); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldDeleted { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_updated() + { + var properties = new StringFieldProperties { IsRequired = true }; + + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name, properties)); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldUpdated { Properties = properties, FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_updated() + { + var properties = new StringFieldProperties { IsRequired = true }; + + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant, properties); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldUpdated { Properties = properties, FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_locked() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).LockField(nestedId.Id, arrayId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldLocked { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_locked() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).LockField(stringId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldLocked { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_hidden() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).HideField(nestedId.Id, arrayId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldHidden { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_hidden() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).HideField(stringId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldHidden { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_shown() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).HideField(nestedId.Id, arrayId.Id); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldShown { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_shown() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).HideField(stringId.Id); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldShown { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_disabled() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).DisableField(nestedId.Id, arrayId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldDisabled { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_disabled() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).DisableField(stringId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldDisabled { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_enabled() + { + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).DisableField(nestedId.Id, arrayId.Id); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldEnabled { FieldId = nestedId, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_field_enabled() + { + var sourceSchema = + new Schema("source") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).DisableField(stringId.Id); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldEnabled { FieldId = stringId } + ); + } + + [Fact] + public void Should_create_events_if_field_created() + { + var sourceSchema = + new Schema("source"); + + var targetSchema = + new Schema("target") + .AddString(stringId.Id, stringId.Name, Partitioning.Invariant).HideField(stringId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + var createdId = NamedId.Of(50L, stringId.Name); + + events.ShouldHaveSameEvents( + new FieldAdded { FieldId = createdId, Name = stringId.Name, Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() }, + new FieldHidden { FieldId = createdId } + ); + } + + [Fact] + public void Should_create_events_if_nested_field_created() + { + var sourceSchema = + new Schema("source"); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(nestedId.Id, nestedId.Name)).HideField(nestedId.Id, arrayId.Id); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + var id1 = NamedId.Of(50L, arrayId.Name); + var id2 = NamedId.Of(51L, stringId.Name); + + events.ShouldHaveSameEvents( + new FieldAdded { FieldId = id1, Name = arrayId.Name, Partitioning = Partitioning.Invariant.Key, Properties = new ArrayFieldProperties() }, + new FieldAdded { FieldId = id2, Name = stringId.Name, ParentFieldId = id1, Properties = new StringFieldProperties() }, + new FieldHidden { FieldId = id2, ParentFieldId = id1 } + ); + } + + [Fact] + public void Should_create_events_if_nested_fields_reordered() + { + var id1 = NamedId.Of(1, "f1"); + var id2 = NamedId.Of(2, "f1"); + + var sourceSchema = + new Schema("source") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(10, "f1") + .AddString(11, "f2")); + + var targetSchema = + new Schema("target") + .AddArray(arrayId.Id, arrayId.Name, Partitioning.Invariant, f => f + .AddString(20, "f2") + .AddString(15, "f1")); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaFieldsReordered { FieldIds = new List { 11, 10 }, ParentFieldId = arrayId } + ); + } + + [Fact] + public void Should_create_events_if_fields_reordered() + { + var id1 = NamedId.Of(1, "f1"); + var id2 = NamedId.Of(2, "f1"); + + var sourceSchema = + new Schema("source") + .AddString(10, "f1", Partitioning.Invariant) + .AddString(11, "f2", Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(20, "f2", Partitioning.Invariant) + .AddString(15, "f1", Partitioning.Invariant); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new SchemaFieldsReordered { FieldIds = new List { 11, 10 } } + ); + } + + [Fact] + public void Should_create_events_if_fields_reordered_after_sync() + { + var id1 = NamedId.Of(1, "f1"); + var id2 = NamedId.Of(2, "f1"); + + var sourceSchema = + new Schema("source") + .AddString(10, "f1", Partitioning.Invariant) + .AddString(11, "f2", Partitioning.Invariant); + + var targetSchema = + new Schema("target") + .AddString(25, "f3", Partitioning.Invariant) + .AddString(20, "f1", Partitioning.Invariant); + + var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); + + events.ShouldHaveSameEvents( + new FieldDeleted { FieldId = NamedId.Of(11L, "f2") }, + new FieldAdded { FieldId = NamedId.Of(50L, "f3"), Name = "f3", Partitioning = Partitioning.Invariant.Key, Properties = new StringFieldProperties() }, + new SchemaFieldsReordered { FieldIds = new List { 50, 10 } } + ); + } + } +} From 0b2bcb411d4d9187e2827a793ac611e2fced2e2e Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 26 Jan 2019 22:45:08 +0100 Subject: [PATCH 2/7] Upsert started. --- .../Schemas/FieldExtensions.cs | 7 + .../Schemas/FieldRegistry.cs | 48 +---- .../Schemas/Schema.cs | 2 +- .../Schemas/Scripts.cs | 46 +++++ .../EventSynchronization/SyncHelpers.cs | 8 - .../Apps/RolePermissionsProvider.cs | 2 +- .../Templates/Builders/AssetFieldBuilder.cs | 2 +- .../Templates/Builders/BooleanFieldBuilder.cs | 2 +- .../Builders/DateTimeFieldBuilder.cs | 2 +- .../Apps/Templates/Builders/FieldBuilder.cs | 4 +- .../Templates/Builders/JsonFieldBuilder.cs | 2 +- .../Templates/Builders/NumberFieldBuilder.cs | 2 +- .../Apps/Templates/Builders/SchemaBuilder.cs | 6 +- .../Templates/Builders/StringFieldBuilder.cs | 2 +- .../Templates/Builders/TagsFieldBuilder.cs | 2 +- .../Templates/CreateBlogCommandMiddleware.cs | 6 +- .../CreateIdentityCommandMiddleware.cs | 15 +- .../Apps/Templates/DefaultScripts.cs | 49 ++++++ .../Backup/GuidMapper.cs | 2 +- .../Backup/RestoreGrain.cs | 2 +- .../Contents/ContentGrain.cs | 10 +- .../Contents/ContentQueryService.cs | 4 +- .../Contents/GraphQL/GraphQLModel.cs | 2 +- .../Contents/Guards/GuardContent.cs | 6 +- .../Contents/SingletonCommandMiddleware.cs | 2 +- .../Schemas/Commands/AddField.cs | 4 +- .../Schemas/Commands/ConfigureScripts.cs | 12 +- .../Schemas/Commands/CreateSchema.cs | 14 +- .../Schemas/Commands/FieldCommand.cs | 4 +- .../Commands/ParentFieldCommand.cs} | 14 +- .../Schemas/Commands/ReorderFields.cs | 4 +- .../Schemas/Commands/SynchronizeSchema.cs | 13 ++ .../Schemas/Commands/UpsertCommand.cs | 101 +++++++++++ ...ateSchemaField.cs => UpsertSchemaField.cs} | 4 +- ...aFieldBase.cs => UpsertSchemaFieldBase.cs} | 2 +- ...tedField.cs => UpsertSchemaNestedField.cs} | 2 +- .../Schemas/Guards/GuardSchema.cs | 121 +++++++------ .../Schemas/ISchemaEntity.cs | 21 --- .../Schemas/SchemaExtensions.cs | 23 ++- .../Schemas/SchemaGrain.cs | 129 +++++++------- .../Schemas/State/SchemaState.cs | 165 ++++-------------- .../Schemas/FieldEvent.cs | 4 +- ...riptsConfigured.cs => ParentFieldEvent.cs} | 15 +- .../Schemas/SchemaCreated.cs | 17 +- .../Schemas/SchemaFieldsReordered.cs | 5 +- .../Generator/SchemasSwaggerGenerator.cs | 2 +- .../Schemas/Models/CreateSchemaDto.cs | 8 +- .../Config/Domain/SerializationServices.cs | 4 +- .../EnrichWithSchemaIdCommandMiddleware.cs | 2 +- src/Squidex/Pipeline/UrlGenerator.cs | 2 +- .../Model/Schemas/FieldRegistryTests.cs | 67 ------- .../SchemaSynchronizerTests.cs | 2 +- .../Apps/Indexes/AppsByNameIndexGrainTests.cs | 69 ++++---- .../Apps/RolePermissionsProviderTests.cs | 3 +- .../Assets/AssetQueryServiceTests.cs | 19 +- .../Backup/BackupReaderWriterTests.cs | 2 +- .../Contents/ContentGrainTests.cs | 16 +- .../Contents/ContentQueryServiceTests.cs | 67 ++++--- .../Contents/GraphQL/GraphQLTestBase.cs | 13 +- .../Contents/Guard/GuardContentTests.cs | 43 ++++- .../Contents/TestData/FakeUrlGenerator.cs | 2 +- .../Schemas/Guards/GuardSchemaTests.cs | 78 ++++----- .../Indexes/SchemasByAppIndexGrainTests.cs | 39 ++--- .../Schemas/SchemaGrainTests.cs | 49 +++--- .../EnrichWithAppIdCommandMiddlewareTests.cs | 38 ++-- ...nrichWithSchemaIdCommandMiddlewareTests.cs | 97 ++++------ .../Migrations/PopulateGrainIndexes.cs | 2 +- tools/Migrate_01/OldEvents/SchemaCreated.cs | 106 +++++++++++ .../Migrate_01/OldEvents/ScriptsConfigured.cs | 65 +++++++ tools/Migrate_01/Rebuilder.cs | 5 +- 70 files changed, 905 insertions(+), 804 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs rename src/Squidex.Domain.Apps.Entities/{Apps/Templates/Scripts.cs => Schemas/Commands/ParentFieldCommand.cs} (60%) create mode 100644 src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs rename src/Squidex.Domain.Apps.Entities/Schemas/Commands/{CreateSchemaField.cs => UpsertSchemaField.cs} (81%) rename src/Squidex.Domain.Apps.Entities/Schemas/Commands/{CreateSchemaFieldBase.cs => UpsertSchemaFieldBase.cs} (93%) rename src/Squidex.Domain.Apps.Entities/Schemas/Commands/{CreateSchemaNestedField.cs => UpsertSchemaNestedField.cs} (87%) rename src/Squidex.Domain.Apps.Events/Schemas/{ScriptsConfigured.cs => ParentFieldEvent.cs} (52%) delete mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs create mode 100644 tools/Migrate_01/OldEvents/SchemaCreated.cs create mode 100644 tools/Migrate_01/OldEvents/ScriptsConfigured.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs index bf34e0911..3447e9271 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs @@ -5,12 +5,19 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Infrastructure; using System.Collections.Generic; +using NamedIdStatic = Squidex.Infrastructure.NamedId; namespace Squidex.Domain.Apps.Core.Schemas { public static class FieldExtensions { + public static NamedId NamedId(this IField field) + { + return NamedIdStatic.Of(field.Id, field.Name); + } + public static Schema ReorderFields(this Schema schema, List ids, long? parentId = null) { if (parentId != null) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs index cd32ac6b7..17876b6e7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs @@ -12,58 +12,26 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Schemas { - public sealed class FieldRegistry + public static class FieldRegistry { - private readonly TypeNameRegistry typeNameRegistry; - private readonly HashSet supportedFields = new HashSet(); - - public FieldRegistry(TypeNameRegistry typeNameRegistry) + public static void Setup(TypeNameRegistry typeNameRegistry) { Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); - this.typeNameRegistry = typeNameRegistry; - var types = typeof(FieldRegistry).Assembly.GetTypes().Where(x => x.BaseType == typeof(FieldProperties)); + var supportedFields = new HashSet(); + foreach (var type in types) { - RegisterField(type); + if (supportedFields.Add(type)) + { + typeNameRegistry.Map(type); + } } typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "References"); typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "DateTime"); } - - private void RegisterField(Type type) - { - if (supportedFields.Add(type)) - { - typeNameRegistry.Map(type); - } - } - - public RootField CreateRootField(long id, string name, Partitioning partitioning, FieldProperties properties, IFieldSettings settings = null) - { - CheckProperties(properties); - - return properties.CreateRootField(id, name, partitioning, settings); - } - - public NestedField CreateNestedField(long id, string name, FieldProperties properties, IFieldSettings settings = null) - { - CheckProperties(properties); - - return properties.CreateNestedField(id, name, settings); - } - - private void CheckProperties(FieldProperties properties) - { - Guard.NotNull(properties, nameof(properties)); - - if (!supportedFields.Contains(properties.GetType())) - { - throw new InvalidOperationException($"The field property '{properties.GetType()}' is not supported."); - } - } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs index 717bec234..05ee7b068 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Schemas } [Pure] - public Schema MoveTo(string category) + public Schema ChangeCategory(string category) { return Clone(clone => { diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs new file mode 100644 index 000000000..30914130d --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs @@ -0,0 +1,46 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public static class Scripts + { + public const string Change = "Change"; + public const string Create = "Create"; + public const string Query = "Query"; + public const string Update = "Update"; + public const string Delete = "Delete"; + + public static string GetChange(this IReadOnlyDictionary scripts) + { + return scripts?.GetOrDefault(Change); + } + + public static string GetCreate(this IReadOnlyDictionary scripts) + { + return scripts?.GetOrDefault(Create); + } + + public static string GetQuery(this IReadOnlyDictionary scripts) + { + return scripts?.GetOrDefault(Query); + } + + public static string GetUpdate(this IReadOnlyDictionary scripts) + { + return scripts?.GetOrDefault(Update); + } + + public static string GetDelete(this IReadOnlyDictionary scripts) + { + return scripts?.GetOrDefault(Delete); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs index 827ccb744..4670750b2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs @@ -6,10 +6,7 @@ // ========================================================================== using System; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.Json; -using NamedIdStatic = Squidex.Infrastructure.NamedId; namespace Squidex.Domain.Apps.Core.EventSynchronization { @@ -30,11 +27,6 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization return lhs.GetType() == rhs.GetType(); } - public static NamedId NamedId(this IField field) - { - return NamedIdStatic.Of(field.Id, field.Name); - } - public static bool EqualsJson(this T lhs, T rhs, IJsonSerializer serializer) { var lhsJson = serializer.Serialize(lhs); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs b/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs index 05b447fc9..07891e285 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs @@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var schemaNames = new List(); schemaNames.Add(Permission.Any); - schemaNames.AddRange(schemas.Select(x => x.Name)); + schemaNames.AddRange(schemas.Select(x => x.SchemaDef.Name)); return schemaNames; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs index 6186f354a..12da92a89 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class AssetFieldBuilder : FieldBuilder { - public AssetFieldBuilder(CreateSchemaField field) + public AssetFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs index 657c1a334..49aae2545 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class BooleanFieldBuilder : FieldBuilder { - public BooleanFieldBuilder(CreateSchemaField field) + public BooleanFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs index 0c53d6cc2..72fc74810 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class DateTimeFieldBuilder : FieldBuilder { - public DateTimeFieldBuilder(CreateSchemaField field) + public DateTimeFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs index 2d519d752..05a1cf535 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs @@ -13,14 +13,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public abstract class FieldBuilder { - private readonly CreateSchemaField field; + private readonly UpsertSchemaField field; protected T Properties() where T : FieldProperties { return field.Properties as T; } - protected FieldBuilder(CreateSchemaField field) + protected FieldBuilder(UpsertSchemaField field) { this.field = field; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs index 15c40c38d..bdf4f3ed7 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class JsonFieldBuilder : FieldBuilder { - public JsonFieldBuilder(CreateSchemaField field) + public JsonFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs index 19f30de32..67245aa34 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class NumberFieldBuilder : FieldBuilder { - public NumberFieldBuilder(CreateSchemaField field) + public NumberFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs index af1e3a7b4..c66ce4d39 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs @@ -105,9 +105,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders return this; } - private CreateSchemaField AddField(string name) where T : FieldProperties, new() + private UpsertSchemaField AddField(string name) where T : FieldProperties, new() { - var field = new CreateSchemaField + var field = new UpsertSchemaField { Name = name.ToCamelCase(), Properties = new T @@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders } }; - command.Fields = command.Fields ?? new List(); + command.Fields = command.Fields ?? new List(); command.Fields.Add(field); return field; diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs index 0fd4b857b..8cd60657d 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs @@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class StringFieldBuilder : FieldBuilder { - public StringFieldBuilder(CreateSchemaField field) + public StringFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs index 707100951..93b4d6611 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { public class TagsFieldBuilder : FieldBuilder { - public TagsFieldBuilder(CreateSchemaField field) + public TagsFieldBuilder(UpsertSchemaField field) : base(field) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs index bac7d168f..c684477ff 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs @@ -115,8 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates await publish(new ConfigureScripts { SchemaId = schemaId.Id, - ScriptCreate = Scripts.Slug, - ScriptUpdate = Scripts.Slug + Scripts = DefaultScripts.GenerateSlug }); return schemaId; @@ -149,8 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates await publish(new ConfigureScripts { SchemaId = schemaId.Id, - ScriptCreate = Scripts.Slug, - ScriptUpdate = Scripts.Slug + Scripts = DefaultScripts.GenerateSlug }); return schemaId; diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs index 616b8a3ff..14927ca2b 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs @@ -18,18 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates public sealed class CreateIdentityCommandMiddleware : ICommandMiddleware { private const string TemplateName = "Identity"; - private const string NormalizeScript = @" - var data = ctx.data; - - if (data.userName && data.userName.iv) { - data.normalizedUserName = { iv: data.userName.iv.toUpperCase() }; - } - - if (data.email && data.email.iv) { - data.normalizedEmail = { iv: data.email.iv.toUpperCase() }; - } - - replace(data);"; public async Task HandleAsync(CommandContext context, Func next) { @@ -250,8 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates await publish(new ConfigureScripts { SchemaId = schemaId.Id, - ScriptCreate = NormalizeScript, - ScriptUpdate = NormalizeScript + Scripts = DefaultScripts.GenerateUsername }); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs new file mode 100644 index 000000000..ba6df11ab --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs @@ -0,0 +1,49 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Apps.Templates +{ + public static class DefaultScripts + { + private const string ScriptToGenerateSlug = @" + var data = ctx.data; + + if (data.title && data.title.iv) { + data.slug = { iv: slugify(data.title.iv) }; + + replace(data); + }"; + + private const string ScriptToGenerateUsername = @" + var data = ctx.data; + + if (data.userName && data.userName.iv) { + data.normalizedUserName = { iv: data.userName.iv.toUpperCase() }; + } + + if (data.email && data.email.iv) { + data.normalizedEmail = { iv: data.email.iv.toUpperCase() }; + } + + replace(data);"; + + public static readonly Dictionary GenerateSlug = new Dictionary + { + [Scripts.Create] = ScriptToGenerateSlug, + [Scripts.Update] = ScriptToGenerateSlug + }; + + public static readonly Dictionary GenerateUsername = new Dictionary + { + [Scripts.Create] = ScriptToGenerateUsername, + [Scripts.Update] = ScriptToGenerateUsername + }; + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs b/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs index 4b1916655..830ace6a8 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Backup { var newGuid = GenerateNewGuid(namedId.Id); - strings[value] = result = new NamedId(newGuid, namedId.Name).ToString(); + strings[value] = result = NamedId.Of(newGuid, namedId.Name).ToString(); return true; } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs index f9e4e3f82..3740d812b 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs @@ -301,7 +301,7 @@ namespace Squidex.Domain.Apps.Entities.Backup if (@event.Payload is AppEvent appEvent && !string.IsNullOrWhiteSpace(CurrentJob.NewAppName)) { - appEvent.AppId = new NamedId(appEvent.AppId.Id, CurrentJob.NewAppName); + appEvent.AppId = NamedId.Of(appEvent.AppId.Id, CurrentJob.NewAppName); } foreach (var handler in handlers) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 06cb8efb6..e2b4e6422 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanCreate(ctx.Schema, c); - await ctx.ExecuteScriptAndTransformAsync(x => x.ScriptCreate, "Create", c, c.Data); + await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Create"), "Create", c, c.Data); await ctx.EnrichAsync(c.Data); if (!c.DoNotValidate) @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (c.Publish) { - await ctx.ExecuteScriptAsync(x => x.ScriptChange, "Published", c, c.Data); + await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), "Published", c, c.Data); } Create(c); @@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents reason = StatusChange.Restored; } - await ctx.ExecuteScriptAsync(x => x.ScriptChange, reason, c, Snapshot.Data); + await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), reason, c, Snapshot.Data); ChangeStatus(c, reason); } @@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanDelete(ctx.Schema, c); - await ctx.ExecuteScriptAsync(x => x.ScriptDelete, "Delete", c, Snapshot.Data); + await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Delete"), "Delete", c, Snapshot.Data); Delete(c); }); @@ -208,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await ctx.ValidateAsync(c.Data); } - newData = await ctx.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Update", c, newData, Snapshot.Data); + newData = await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Update"), "Update", c, newData, Snapshot.Data); if (isProposal) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 89b566f2c..a81b6017e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -158,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var converters = GenerateConverters(context, checkType).ToArray(); - var scriptText = schema.ScriptQuery; + var scriptText = schema.SchemaDef.Scripts.GetOrDefault("Query"); var isScripting = !string.IsNullOrWhiteSpace(scriptText); @@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private static void CheckPermission(ISchemaEntity schema, ClaimsPrincipal user) { var permissions = user.Permissions(); - var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.Name); + var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.SchemaDef.Name); if (!permissions.Allows(permission)) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index e35b346dc..56521688b 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL assetType = new AssetGraphType(this); assetListType = new ListGraphType(new NonNullGraphType(assetType)); - schemasById = schemas.Where(x => x.IsPublished).ToDictionary(x => x.Id); + schemasById = schemas.Where(x => x.SchemaDef.IsPublished).ToDictionary(x => x.Id); graphQLSchema = BuildSchema(this); graphQLSchema.RegisterValueConverter(JsonConverter.Instance); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 194432381..379bd601f 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards ValidateData(command, e); }); - if (schema.IsSingleton && command.ContentId != schema.Id) + if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id) { throw new DomainException("Singleton content cannot be created."); } @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { Guard.NotNull(command, nameof(command)); - if (schema.IsSingleton && command.Status != Status.Published) + if (schema.SchemaDef.IsSingleton && command.Status != Status.Published) { throw new DomainException("Singleton content archived or unpublished."); } @@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { Guard.NotNull(command, nameof(command)); - if (schema.IsSingleton) + if (schema.SchemaDef.IsSingleton) { throw new DomainException("Singleton content cannot be deleted."); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs index 401a0a67b..4b11b746e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents context.Command is CreateSchema createSchema && createSchema.Singleton) { - var schemaId = new NamedId(createSchema.SchemaId, createSchema.Name); + var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name); var data = new NamedContentData(); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs index 09cc26f0a..92d15b149 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs @@ -9,10 +9,8 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class AddField : SchemaCommand + public sealed class AddField : ParentFieldCommand { - public long? ParentFieldId { get; set; } - public string Name { get; set; } public string Partitioning { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs index d850076a6..7012a91c1 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs @@ -5,18 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; + namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class ConfigureScripts : SchemaCommand { - public string ScriptQuery { get; set; } - - public string ScriptCreate { get; set; } - - public string ScriptUpdate { get; set; } - - public string ScriptDelete { get; set; } - - public string ScriptChange { get; set; } + public Dictionary Scripts { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs index 9c39fa5a9..22c4b746b 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs @@ -8,27 +8,25 @@ using System; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using SchemaFields = System.Collections.Generic.List; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class CreateSchema : SchemaCommand, IAppCommand + public sealed class CreateSchema : UpsertCommand, IAppCommand { public NamedId AppId { get; set; } public string Name { get; set; } - public SchemaFields Fields { get; set; } - - public SchemaProperties Properties { get; set; } - public bool Singleton { get; set; } - public bool Publish { get; set; } - public CreateSchema() { SchemaId = Guid.NewGuid(); } + + public Schema ToSchema() + { + return ToSchema(Name, Singleton); + } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs index 6246ad94c..6e486cb11 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs @@ -7,10 +7,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public class FieldCommand : SchemaCommand + public class FieldCommand : ParentFieldCommand { - public long? ParentFieldId { get; set; } - public long FieldId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ParentFieldCommand.cs similarity index 60% rename from src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/ParentFieldCommand.cs index 51d2c72fc..91f067fb1 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ParentFieldCommand.cs @@ -5,18 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.Apps.Templates +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public static class Scripts + public abstract class ParentFieldCommand : SchemaCommand { - public const string Slug = -@"var data = ctx.data; - -if (data.title && data.title.iv) { - data.slug = { iv: slugify(data.title.iv) }; - - replace(data); -} -"; + public long? ParentFieldId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs index 4c452d2ff..227443431 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs @@ -9,10 +9,8 @@ using System.Collections.Generic; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class ReorderFields : SchemaCommand + public sealed class ReorderFields : ParentFieldCommand { - public long? ParentFieldId { get; set; } - public List FieldIds { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs new file mode 100644 index 000000000..97b093dd5 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands +{ + public sealed class SynchronizeSchema : UpsertCommand + { + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs new file mode 100644 index 000000000..8e4b18427 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs @@ -0,0 +1,101 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Schemas; +using SchemaFields = System.Collections.Generic.List; + +namespace Squidex.Domain.Apps.Entities.Schemas.Commands +{ + public abstract class UpsertCommand : SchemaCommand + { + public bool Publish { get; set; } + + public string Category { get; set; } + + public SchemaFields Fields { get; set; } + + public SchemaProperties Properties { get; set; } + + public Dictionary Scripts { get; set; } + + public Dictionary PreviewUrls { get; set; } + + public Schema ToSchema(string name, bool isSingleton) + { + var schema = new Schema(name, Properties, isSingleton); + + if (Publish) + { + schema = schema.Publish(); + } + + var totalFields = 0; + + if (Fields != null) + { + foreach (var eventField in Fields) + { + totalFields++; + + var partitioning = Partitioning.FromString(eventField.Partitioning); + + var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning); + + if (field is ArrayField arrayField && eventField.Nested?.Count > 0) + { + foreach (var nestedEventField in eventField.Nested) + { + totalFields++; + + var nestedField = nestedEventField.Properties.CreateNestedField(totalFields, nestedEventField.Name); + + if (nestedEventField.IsHidden) + { + nestedField = nestedField.Hide(); + } + + if (nestedEventField.IsDisabled) + { + nestedField = nestedField.Disable(); + } + + if (nestedEventField.IsLocked) + { + nestedField = nestedField.Lock(); + } + + arrayField = arrayField.AddField(nestedField); + } + + field = arrayField; + } + + if (eventField.IsHidden) + { + field = field.Hide(); + } + + if (eventField.IsDisabled) + { + field = field.Disable(); + } + + if (eventField.IsLocked) + { + field = field.Lock(); + } + + schema = schema.AddField(field); + } + } + + return schema; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs similarity index 81% rename from src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs index 6dfba43fc..cc92d2e8f 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs @@ -9,10 +9,10 @@ using System.Collections.Generic; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class CreateSchemaField : CreateSchemaFieldBase + public sealed class UpsertSchemaField : UpsertSchemaFieldBase { public string Partitioning { get; set; } = "invariant"; - public List Nested { get; set; } + public List Nested { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaFieldBase.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs similarity index 93% rename from src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaFieldBase.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs index 3d91afc20..4b165a145 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaFieldBase.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public abstract class CreateSchemaFieldBase + public abstract class UpsertSchemaFieldBase { public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaNestedField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaNestedField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaNestedField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaNestedField.cs index 55cd4e6eb..c35218acc 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaNestedField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaNestedField.cs @@ -7,7 +7,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { - public sealed class CreateSchemaNestedField : CreateSchemaFieldBase + public sealed class UpsertSchemaNestedField : UpsertSchemaFieldBase { } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 2df48f6e0..65b22cbe9 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -32,60 +32,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards e("A schema with the same name already exists."); } - if (command.Fields?.Count > 0) - { - var fieldIndex = 0; - var fieldPrefix = string.Empty; - - foreach (var field in command.Fields) - { - fieldIndex++; - fieldPrefix = $"Fields[{fieldIndex}]"; - - if (!field.Partitioning.IsValidPartitioning()) - { - e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}"); - } - - ValidateField(e, fieldPrefix, field); - - if (field.Nested?.Count > 0) - { - if (field.Properties is ArrayFieldProperties) - { - var nestedIndex = 0; - var nestedPrefix = string.Empty; - - foreach (var nestedField in field.Nested) - { - nestedIndex++; - nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]"; - - if (nestedField.Properties is ArrayFieldProperties) - { - e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}"); - } - - ValidateField(e, nestedPrefix, nestedField); - } - } - else if (field.Nested.Count > 0) - { - e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}"); - } + ValidateUpsert(command, e); + }); + } - if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count) - { - e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested"); - } - } - } + public static void CanSynchronize(SynchronizeSchema command) + { + Guard.NotNull(command, nameof(command)); - if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count) - { - e("Fields cannot have duplicate names.", nameof(command.Fields)); - } - } + Validate.It(() => "Cannot synchronize schema.", e => + { + ValidateUpsert(command, e); }); } @@ -171,7 +128,65 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards Guard.NotNull(command, nameof(command)); } - private static void ValidateField(AddValidation e, string prefix, CreateSchemaFieldBase field) + private static void ValidateUpsert(UpsertCommand command, AddValidation e) + { + if (command.Fields?.Count > 0) + { + var fieldIndex = 0; + var fieldPrefix = string.Empty; + + foreach (var field in command.Fields) + { + fieldIndex++; + fieldPrefix = $"Fields[{fieldIndex}]"; + + if (!field.Partitioning.IsValidPartitioning()) + { + e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}"); + } + + ValidateField(field, fieldPrefix, e); + + if (field.Nested?.Count > 0) + { + if (field.Properties is ArrayFieldProperties) + { + var nestedIndex = 0; + var nestedPrefix = string.Empty; + + foreach (var nestedField in field.Nested) + { + nestedIndex++; + nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]"; + + if (nestedField.Properties is ArrayFieldProperties) + { + e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}"); + } + + ValidateField(nestedField, nestedPrefix, e); + } + } + else if (field.Nested.Count > 0) + { + e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}"); + } + + if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count) + { + e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested"); + } + } + } + + if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count) + { + e("Fields cannot have duplicate names.", nameof(command.Fields)); + } + } + } + + private static void ValidateField(UpsertSchemaFieldBase field, string prefix, AddValidation e) { if (!field.Name.IsPropertyName()) { diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs index 871d04314..05b20f405 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; @@ -20,28 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas { NamedId AppId { get; } - string Name { get; } - - string Category { get; } - - bool IsSingleton { get; } - - bool IsPublished { get; } - bool IsDeleted { get; } - string ScriptQuery { get; } - - string ScriptCreate { get; } - - string ScriptUpdate { get; } - - string ScriptDelete { get; } - - string ScriptChange { get; } - Schema SchemaDef { get; } - - Dictionary PreviewUrls { get; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs index 3a44a25f9..d2f82939e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs @@ -8,6 +8,7 @@ using System; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using StaticNamedId = Squidex.Infrastructure.NamedId; namespace Squidex.Domain.Apps.Entities.Schemas { @@ -15,7 +16,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas { public static NamedId NamedId(this ISchemaEntity schema) { - return new NamedId(schema.Id, schema.Name); + return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name); + } + + public static long MaxId(this Schema schema) + { + var id = 0L; + + foreach (var field in schema.Fields) + { + if (field is IArrayField arrayField) + { + foreach (var nestedField in arrayField.Fields) + { + id = Math.Max(id, nestedField.Id); + } + } + + id = Math.Max(id, field.Id); + } + + return id; } public static string EscapePartition(this string value) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 2aff00cc3..86e923b6d 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -6,8 +6,8 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.EventSynchronization; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Guards; @@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Reflection; @@ -27,17 +28,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas public sealed class SchemaGrain : SquidexDomainObjectGrain, ISchemaGrain { private readonly IAppProvider appProvider; - private readonly FieldRegistry registry; + private readonly IJsonSerializer serializer; - public SchemaGrain(IStore store, ISemanticLog log, IAppProvider appProvider, FieldRegistry registry) + public SchemaGrain(IStore store, ISemanticLog log, IAppProvider appProvider, IJsonSerializer serializer) : base(store, log) { Guard.NotNull(appProvider, nameof(appProvider)); - Guard.NotNull(registry, nameof(registry)); + Guard.NotNull(serializer, nameof(serializer)); this.appProvider = appProvider; - this.registry = registry; + this.serializer = serializer; } protected override Task ExecuteAsync(IAggregateCommand command) @@ -200,45 +201,32 @@ namespace Squidex.Domain.Apps.Entities.Schemas } } - public void Create(CreateSchema command) + public void Synchronize(SynchronizeSchema command) { - var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = NamedId.Of(command.SchemaId, command.Name) }); - - if (command.Fields != null) - { - @event.Fields = new List(); - - foreach (var commandField in command.Fields) - { - var eventField = SimpleMapper.Map(commandField, new SchemaCreatedField()); - - @event.Fields.Add(eventField); + var schemaSource = Snapshot.SchemaDef; + var schemaTarget = command.ToSchema(schemaSource.Name, schemaSource.IsSingleton); - if (commandField.Nested != null) - { - eventField.Nested = new List(); - - foreach (var nestedField in commandField.Nested) - { - var eventNestedField = SimpleMapper.Map(nestedField, new SchemaCreatedNestedField()); + var @events = schemaTarget.Synchronize(schemaSource, serializer, () => Snapshot.SchemaFieldsTotal + 1); - eventField.Nested.Add(eventNestedField); - } - } - } + foreach (var @event in @events) + { + RaiseEvent(@event); } + } - RaiseEvent(@event); + public void Create(CreateSchema command) + { + RaiseEvent(command, new SchemaCreated { SchemaId = NamedId.Of(command.SchemaId, command.Name), Schema = command.ToSchema() }); } public void Add(AddField command) { - RaiseEvent(SimpleMapper.Map(command, new FieldAdded { ParentFieldId = GetFieldId(command.ParentFieldId), FieldId = CreateFieldId(command) })); + RaiseEvent(command, new FieldAdded { FieldId = NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name) }); } public void UpdateField(UpdateField command) { - RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated())); + RaiseEvent(command, new FieldUpdated()); } public void LockField(LockField command) @@ -273,88 +261,86 @@ namespace Squidex.Domain.Apps.Entities.Schemas public void Reorder(ReorderFields command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered { ParentFieldId = GetFieldId(command.ParentFieldId) })); + RaiseEvent(command, new SchemaFieldsReordered()); } public void Publish(PublishSchema command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaPublished())); + RaiseEvent(command, new SchemaPublished()); } public void Unpublish(UnpublishSchema command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished())); + RaiseEvent(command, new SchemaUnpublished()); } public void ConfigureScripts(ConfigureScripts command) { - RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); + RaiseEvent(command, new SchemaScriptsConfigured()); } public void ChangeCategory(ChangeCategory command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaCategoryChanged())); + RaiseEvent(command, new SchemaCategoryChanged()); } public void ConfigurePreviewUrls(ConfigurePreviewUrls command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaPreviewUrlsConfigured())); + RaiseEvent(command, new SchemaPreviewUrlsConfigured()); } - public void Delete(DeleteSchema command) + public void Update(UpdateSchema command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); + RaiseEvent(command, new SchemaUpdated()); } - public void Update(UpdateSchema command) + public void Delete(DeleteSchema command) { - RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated())); + RaiseEvent(command, new SchemaDeleted()); } - private void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) + private void RaiseEvent(TCommand command, TEvent @event) where TCommand : SchemaCommand where TEvent : SchemaEvent { - SimpleMapper.Map(fieldCommand, @event); + SimpleMapper.Map(command, @event); - if (fieldCommand.ParentFieldId.HasValue) + NamedId GetFieldId(long? id) { - if (Snapshot.SchemaDef.FieldsById.TryGetValue(fieldCommand.ParentFieldId.Value, out var field)) + if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field)) { - @event.ParentFieldId = NamedId.Of(field.Id, field.Name); + return NamedId.Of(field.Id, field.Name); + } + + return null; + } - if (field is IArrayField arrayField && arrayField.FieldsById.TryGetValue(fieldCommand.FieldId, out var nestedField)) + if (command is ParentFieldCommand pc && @event is ParentFieldEvent pe) + { + if (pc.ParentFieldId.HasValue) + { + if (Snapshot.SchemaDef.FieldsById.TryGetValue(pc.ParentFieldId.Value, out var field)) { - @event.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); + pe.ParentFieldId = NamedId.Of(field.Id, field.Name); + + if (field is IArrayField arrayField && command is FieldCommand fc && @event is FieldEvent fe && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField)) + { + fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); + } } } - } - else - { - @event.FieldId = GetFieldId(fieldCommand.FieldId); + else if (command is FieldCommand fc && @event is FieldEvent fe) + { + fe.FieldId = GetFieldId(fc.FieldId); + } } RaiseEvent(@event); } - private NamedId CreateFieldId(AddField command) - { - return NamedId.Of(Snapshot.TotalFields + 1L, command.Name); - } - - private NamedId GetFieldId(long? id) - { - if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field)) - { - return NamedId.Of(field.Id, field.Name); - } - - return null; - } - private void RaiseEvent(SchemaEvent @event) { if (@event.SchemaId == null) { - @event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.Name); + @event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.SchemaDef.Name); } if (@event.AppId == null) @@ -365,6 +351,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas RaiseEvent(Envelope.Create(@event)); } + private NamedId CreateFieldId(AddField command) + { + return NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name); + } + private void VerifyNotDeleted() { if (Snapshot.IsDeleted) @@ -375,7 +366,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas protected override SchemaState OnEvent(Envelope @event) { - return Snapshot.Apply(@event, registry); + return Snapshot.Apply(@event); } public Task> GetStateAsync() diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs index 6e8a837f3..4cc1b47c5 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.Runtime.Serialization; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; @@ -15,7 +14,6 @@ using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; namespace Squidex.Domain.Apps.Entities.Schemas.State @@ -27,128 +25,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State public NamedId AppId { get; set; } [DataMember] - public string Name { get; set; } - - [DataMember] - public string Category { get; set; } - - [DataMember] - public int TotalFields { get; set; } + public long SchemaFieldsTotal { get; set; } [DataMember] public bool IsDeleted { get; set; } - [DataMember] - public bool IsSingleton { get; set; } - - [DataMember] - public string ScriptQuery { get; set; } - - [DataMember] - public string ScriptCreate { get; set; } - - [DataMember] - public string ScriptUpdate { get; set; } - - [DataMember] - public string ScriptDelete { get; set; } - - [DataMember] - public string ScriptChange { get; set; } - - [DataMember] - public Dictionary PreviewUrls { get; set; } - [DataMember] public Schema SchemaDef { get; set; } - [IgnoreDataMember] - public bool IsPublished + protected void On(SchemaCreated @event) { - get { return SchemaDef.IsPublished; } - } - - protected void On(SchemaCreated @event, FieldRegistry registry) - { - Name = @event.Name; - - IsSingleton = @event.Singleton; - - var schema = new Schema(@event.Name); - - if (@event.Properties != null) - { - schema = schema.Update(@event.Properties); - } - - if (@event.Publish) - { - schema = schema.Publish(); - } - - if (@event.Fields != null) - { - foreach (var eventField in @event.Fields) - { - TotalFields++; - - var partitioning = Partitioning.FromString(eventField.Partitioning); - - var field = registry.CreateRootField(TotalFields, eventField.Name, partitioning, eventField.Properties); - - if (field is ArrayField arrayField && eventField.Nested?.Count > 0) - { - foreach (var nestedEventField in eventField.Nested) - { - TotalFields++; - - var nestedField = registry.CreateNestedField(TotalFields, nestedEventField.Name, nestedEventField.Properties); - - if (nestedEventField.IsHidden) - { - nestedField = nestedField.Hide(); - } - - if (nestedEventField.IsDisabled) - { - nestedField = nestedField.Disable(); - } - - arrayField = arrayField.AddField(nestedField); - } - - field = arrayField; - } - - if (eventField.IsHidden) - { - field = field.Hide(); - } - - if (eventField.IsDisabled) - { - field = field.Disable(); - } - - if (eventField.IsLocked) - { - field = field.Lock(); - } - - schema = schema.AddField(field); - } - } - - SchemaDef = schema; + SchemaDef = @event.Schema; + SchemaFieldsTotal = @event.Schema.MaxId(); AppId = @event.AppId; } - protected void On(FieldAdded @event, FieldRegistry registry) + protected void On(FieldAdded @event) { if (@event.ParentFieldId != null) { - var field = registry.CreateNestedField(@event.FieldId.Id, @event.Name, @event.Properties); + var field = @event.Properties.CreateNestedField(@event.FieldId.Id, @event.Name); SchemaDef = SchemaDef.UpdateField(@event.ParentFieldId.Id, x => ((ArrayField)x).AddField(field)); } @@ -156,95 +53,95 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State { var partitioning = Partitioning.FromString(@event.Partitioning); - var field = registry.CreateRootField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties); + var field = @event.Properties.CreateRootField(@event.FieldId.Id, @event.Name, partitioning); SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id); SchemaDef = SchemaDef.AddField(field); } - TotalFields++; + SchemaFieldsTotal++; } - protected void On(SchemaCategoryChanged @event, FieldRegistry registry) + protected void On(SchemaCategoryChanged @event) { - Category = @event.Name; + SchemaDef = SchemaDef.ChangeCategory(@event.Name); } - protected void On(SchemaPreviewUrlsConfigured @event, FieldRegistry registry) + protected void On(SchemaPreviewUrlsConfigured @event) { - PreviewUrls = @event.PreviewUrls; + SchemaDef = SchemaDef.ConfigurePreviewUrls(@event.PreviewUrls); } - protected void On(SchemaPublished @event, FieldRegistry registry) + protected void On(SchemaScriptsConfigured @event) + { + SchemaDef = SchemaDef.ConfigureScripts(@event.Scripts); + } + + protected void On(SchemaPublished @event) { SchemaDef = SchemaDef.Publish(); } - protected void On(SchemaUnpublished @event, FieldRegistry registry) + protected void On(SchemaUnpublished @event) { SchemaDef = SchemaDef.Unpublish(); } - protected void On(SchemaUpdated @event, FieldRegistry registry) + protected void On(SchemaUpdated @event) { SchemaDef = SchemaDef.Update(@event.Properties); } - protected void On(SchemaFieldsReordered @event, FieldRegistry registry) + protected void On(SchemaFieldsReordered @event) { SchemaDef = SchemaDef.ReorderFields(@event.FieldIds, @event.ParentFieldId?.Id); } - protected void On(FieldUpdated @event, FieldRegistry registry) + protected void On(FieldUpdated @event) { SchemaDef = SchemaDef.UpdateField(@event.FieldId.Id, @event.Properties, @event.ParentFieldId?.Id); } - protected void On(FieldLocked @event, FieldRegistry registry) + protected void On(FieldLocked @event) { SchemaDef = SchemaDef.LockField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(FieldDisabled @event, FieldRegistry registry) + protected void On(FieldDisabled @event) { SchemaDef = SchemaDef.DisableField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(FieldEnabled @event, FieldRegistry registry) + protected void On(FieldEnabled @event) { SchemaDef = SchemaDef.EnableField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(FieldHidden @event, FieldRegistry registry) + protected void On(FieldHidden @event) { SchemaDef = SchemaDef.HideField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(FieldShown @event, FieldRegistry registry) + protected void On(FieldShown @event) { SchemaDef = SchemaDef.ShowField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(FieldDeleted @event, FieldRegistry registry) + protected void On(FieldDeleted @event) { SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id, @event.ParentFieldId?.Id); } - protected void On(SchemaDeleted @event, FieldRegistry registry) + protected void On(SchemaDeleted @event) { IsDeleted = true; } - protected void On(ScriptsConfigured @event, FieldRegistry registry) - { - SimpleMapper.Map(@event, this); - } - - public SchemaState Apply(Envelope @event, FieldRegistry registry) + public SchemaState Apply(Envelope @event) { var payload = (SquidexEvent)@event.Payload; - return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload, registry)); + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); } } } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs b/src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs index 0c2a9690f..41e33e860 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs @@ -9,10 +9,8 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Events.Schemas { - public abstract class FieldEvent : SchemaEvent + public abstract class FieldEvent : ParentFieldEvent { public NamedId FieldId { get; set; } - - public NamedId ParentFieldId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/ScriptsConfigured.cs b/src/Squidex.Domain.Apps.Events/Schemas/ParentFieldEvent.cs similarity index 52% rename from src/Squidex.Domain.Apps.Events/Schemas/ScriptsConfigured.cs rename to src/Squidex.Domain.Apps.Events/Schemas/ParentFieldEvent.cs index 3d8abe58a..0406c3c40 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/ScriptsConfigured.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/ParentFieldEvent.cs @@ -5,21 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Events.Schemas { - [EventType(nameof(ScriptsConfigured))] - public sealed class ScriptsConfigured : SchemaEvent + public abstract class ParentFieldEvent : SchemaEvent { - public string ScriptQuery { get; set; } - - public string ScriptCreate { get; set; } - - public string ScriptUpdate { get; set; } - - public string ScriptDelete { get; set; } - - public string ScriptChange { get; set; } + public NamedId ParentFieldId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs index 6f4f24295..703781d78 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) @@ -7,21 +7,12 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.EventSourcing; -using SchemaFields = System.Collections.Generic.List; namespace Squidex.Domain.Apps.Events.Schemas { - [EventType(nameof(SchemaCreated))] + [EventType(nameof(SchemaCreated), 2)] public sealed class SchemaCreated : SchemaEvent { - public string Name { get; set; } - - public SchemaFields Fields { get; set; } - - public SchemaProperties Properties { get; set; } - - public bool Singleton { get; set; } - - public bool Publish { get; set; } + public Schema Schema { get; set; } } -} +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs index 50ad79ed8..1c1f4f7c4 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs @@ -6,16 +6,13 @@ // ========================================================================== using System.Collections.Generic; -using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Schemas { [EventType(nameof(SchemaFieldsReordered))] - public sealed class SchemaFieldsReordered : SchemaEvent + public sealed class SchemaFieldsReordered : ParentFieldEvent { - public NamedId ParentFieldId { get; set; } - public List FieldIds { get; set; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs index 7c90c3ed6..0ab353afb 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs @@ -77,7 +77,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { var appBasePath = $"/content/{app.Name}"; - foreach (var schema in schemas.Where(x => x.IsPublished).Select(x => x.SchemaDef)) + foreach (var schema in schemas.Select(x => x.SchemaDef).Where(x => x.IsPublished)) { new SchemaSwaggerGenerator(document, app.Name, appBasePath, schema, AppendSchema, app.PartitionResolver()).GenerateSchemaOperations(); } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs index 922647abc..b18eb7d62 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs @@ -57,21 +57,21 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models if (Fields != null) { - command.Fields = new List(); + command.Fields = new List(); foreach (var fieldDto in Fields) { var rootProperties = fieldDto?.Properties.ToProperties(); - var rootField = SimpleMapper.Map(fieldDto, new CreateSchemaField { Properties = rootProperties }); + var rootField = SimpleMapper.Map(fieldDto, new UpsertSchemaField { Properties = rootProperties }); if (fieldDto.Nested != null) { - rootField.Nested = new List(); + rootField.Nested = new List(); foreach (var nestedFieldDto in fieldDto.Nested) { var nestedProperties = nestedFieldDto?.Properties.ToProperties(); - var nestedField = SimpleMapper.Map(nestedFieldDto, new CreateSchemaNestedField { Properties = nestedProperties }); + var nestedField = SimpleMapper.Map(nestedFieldDto, new UpsertSchemaNestedField { Properties = nestedProperties }); rootField.Nested.Add(nestedField); } diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index 87916c8ac..9ec50ed28 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -30,7 +30,6 @@ namespace Squidex.Config.Domain .MapUnmapped(SquidexEvents.Assembly) .MapUnmapped(SquidexInfrastructure.Assembly) .MapUnmapped(SquidexMigrations.Assembly); - private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry); public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings(); public static readonly JsonSerializer DefaultJsonSerializer; @@ -70,6 +69,8 @@ namespace Squidex.Config.Domain static SerializationServices() { + FieldRegistry.Setup(TypeNameRegistry); + ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto); DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings); @@ -77,7 +78,6 @@ namespace Squidex.Config.Domain public static IServiceCollection AddMySerializers(this IServiceCollection services) { - services.AddSingleton(FieldRegistry); services.AddSingleton(DefaultJsonSettings); services.AddSingleton(DefaultJsonSerializer); services.AddSingleton(TypeNameRegistry); diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index c2851e609..f2d5ae42b 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -97,7 +97,7 @@ namespace Squidex.Pipeline.CommandMiddlewares throw new DomainObjectNotFoundException(schemaName, typeof(ISchemaEntity)); } - return NamedId.Of(schema.Id, schema.Name); + return schema.NamedId(); } } diff --git a/src/Squidex/Pipeline/UrlGenerator.cs b/src/Squidex/Pipeline/UrlGenerator.cs index f30395986..d2c1266e7 100644 --- a/src/Squidex/Pipeline/UrlGenerator.cs +++ b/src/Squidex/Pipeline/UrlGenerator.cs @@ -57,7 +57,7 @@ namespace Squidex.Pipeline public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content) { - return urlsOptions.BuildUrl($"api/content/{app.Name}/{schema.Name}/{content.Id}"); + return urlsOptions.BuildUrl($"api/content/{app.Name}/{schema.SchemaDef.Name}/{content.Id}"); } public string GenerateContentUIUrl(NamedId appId, NamedId schemaId, Guid contentId) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs deleted file mode 100644 index 3653c1f18..000000000 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure; -using Xunit; - -namespace Squidex.Domain.Apps.Core.Model.Schemas -{ - public class FieldRegistryTests - { - private readonly FieldRegistry sut = new FieldRegistry(new TypeNameRegistry()); - - private sealed class InvalidProperties : FieldProperties - { - public override T Accept(IFieldPropertiesVisitor visitor) - { - return default; - } - - public override T Accept(IFieldVisitor visitor, IField field) - { - return default; - } - - public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) - { - return null; - } - - public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) - { - return null; - } - } - - [Fact] - public void Should_throw_exception_if_creating_field_and_field_is_not_registered() - { - Assert.Throws(() => sut.CreateRootField(1, "name", Partitioning.Invariant, new InvalidProperties())); - } - - [Theory] - [InlineData(typeof(AssetsFieldProperties))] - [InlineData(typeof(BooleanFieldProperties))] - [InlineData(typeof(DateTimeFieldProperties))] - [InlineData(typeof(GeolocationFieldProperties))] - [InlineData(typeof(JsonFieldProperties))] - [InlineData(typeof(NumberFieldProperties))] - [InlineData(typeof(ReferencesFieldProperties))] - [InlineData(typeof(StringFieldProperties))] - [InlineData(typeof(TagsFieldProperties))] - public void Should_create_field_by_properties(Type propertyType) - { - var properties = (FieldProperties)Activator.CreateInstance(propertyType); - - var field = sut.CreateRootField(1, "name", Partitioning.Invariant, properties); - - Assert.Equal(properties, field.RawProperties); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs index e6394ccb7..e52c74a7e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization public void Should_create_events_if_category_changed() { var sourceSchema = new Schema("source"); - var targetSchema = new Schema("target").MoveTo("Category"); + var targetSchema = new Schema("target").ChangeCategory("Category"); var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs index f5cf63deb..fd122b7c9 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; +using Squidex.Infrastructure; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; using Xunit; @@ -19,10 +20,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes { private readonly IStore store = A.Fake>(); private readonly IPersistence persistence = A.Fake>(); - private readonly Guid appId1 = Guid.NewGuid(); - private readonly Guid appId2 = Guid.NewGuid(); - private readonly string appName1 = "my-app1"; - private readonly string appName2 = "my-app2"; + private readonly NamedId appId1 = NamedId.Of(Guid.NewGuid(), "my-app1"); + private readonly NamedId appId2 = NamedId.Of(Guid.NewGuid(), "my-app2"); private readonly AppsByNameIndexGrain sut; public AppsByNameIndexGrainTests() @@ -37,11 +36,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes [Fact] public async Task Should_add_app_id_to_index() { - await sut.AddAppAsync(appId1, appName1); + await sut.AddAppAsync(appId1.Id, appId1.Name); - var result = await sut.GetAppIdAsync(appName1); + var result = await sut.GetAppIdAsync(appId1.Name); - Assert.Equal(appId1, result); + Assert.Equal(appId1.Id, result); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .MustHaveHappened(); @@ -50,79 +49,79 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes [Fact] public async Task Should_not_be_able_to_reserve_index_if_name_taken() { - await sut.AddAppAsync(appId2, appName1); + await sut.AddAppAsync(appId2.Id, appId1.Name); - Assert.False(await sut.ReserveAppAsync(appId1, appName1)); + Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); } [Fact] public async Task Should_not_be_able_to_reserve_if_name_reserved() { - await sut.ReserveAppAsync(appId2, appName1); + await sut.ReserveAppAsync(appId2.Id, appId1.Name); - Assert.False(await sut.ReserveAppAsync(appId1, appName1)); + Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); } [Fact] public async Task Should_not_be_able_to_reserve_if_id_taken() { - await sut.AddAppAsync(appId1, appName1); + await sut.AddAppAsync(appId1.Id, appId1.Name); - Assert.False(await sut.ReserveAppAsync(appId1, appName2)); + Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name)); } [Fact] public async Task Should_not_be_able_to_reserve_if_id_reserved() { - await sut.ReserveAppAsync(appId1, appName1); + await sut.ReserveAppAsync(appId1.Id, appId1.Name); - Assert.False(await sut.ReserveAppAsync(appId1, appName2)); + Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name)); } [Fact] public async Task Should_be_able_to_reserve_if_id_and_name_not_reserved() { - await sut.ReserveAppAsync(appId1, appName1); + await sut.ReserveAppAsync(appId1.Id, appId1.Name); - Assert.True(await sut.ReserveAppAsync(appId2, appName2)); + Assert.True(await sut.ReserveAppAsync(appId2.Id, appId2.Name)); } [Fact] public async Task Should_be_able_to_reserve_after_app_removed() { - await sut.AddAppAsync(appId1, appName1); - await sut.RemoveAppAsync(appId1); + await sut.AddAppAsync(appId1.Id, appId1.Name); + await sut.RemoveAppAsync(appId1.Id); - Assert.True(await sut.ReserveAppAsync(appId1, appName1)); + Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); } [Fact] public async Task Should_be_able_to_reserve_after_reservation_removed() { - await sut.ReserveAppAsync(appId1, appName1); - await sut.RemoveReservationAsync(appId1, appName1); + await sut.ReserveAppAsync(appId1.Id, appId1.Name); + await sut.RemoveReservationAsync(appId1.Id, appId1.Name); - Assert.True(await sut.ReserveAppAsync(appId1, appName1)); + Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name)); } [Fact] public async Task Should_return_many_app_ids() { - await sut.AddAppAsync(appId1, appName1); - await sut.AddAppAsync(appId2, appName2); + await sut.AddAppAsync(appId1.Id, appId1.Name); + await sut.AddAppAsync(appId2.Id, appId2.Name); - var ids = await sut.GetAppIdsAsync(appName1, appName2); + var ids = await sut.GetAppIdsAsync(appId1.Name, appId2.Name); - Assert.Equal(new List { appId1, appId2 }, ids); + Assert.Equal(new List { appId1.Id, appId2.Id }, ids); } [Fact] public async Task Should_remove_app_id_from_index() { - await sut.AddAppAsync(appId1, appName1); - await sut.RemoveAppAsync(appId1); + await sut.AddAppAsync(appId1.Id, appId1.Name); + await sut.RemoveAppAsync(appId1.Id); - var result = await sut.GetAppIdAsync(appName1); + var result = await sut.GetAppIdAsync(appId1.Name); Assert.Equal(Guid.Empty, result); @@ -135,16 +134,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes { var state = new Dictionary { - [appName1] = appId1, - [appName2] = appId2 + [appId1.Name] = appId1.Id, + [appId2.Name] = appId2.Id }; await sut.RebuildAsync(state); - Assert.Equal(appId1, await sut.GetAppIdAsync(appName1)); - Assert.Equal(appId2, await sut.GetAppIdAsync(appName2)); + Assert.Equal(appId1.Id, await sut.GetAppIdAsync(appId1.Name)); + Assert.Equal(appId2.Id, await sut.GetAppIdAsync(appId2.Name)); - Assert.Equal(new List { appId1, appId2 }, await sut.GetAppIdsAsync()); + Assert.Equal(new List { appId1.Id, appId2.Id }, await sut.GetAppIdsAsync()); Assert.Equal(2, await sut.CountAsync()); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs index ae797b146..dbee889b1 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Xunit; @@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { var schema = A.Fake(); - A.CallTo(() => schema.Name).Returns(name); + A.CallTo(() => schema.SchemaDef).Returns(new Schema(name)); return schema; } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs index ba52baa49..f5520618b 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs @@ -26,8 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Assets private readonly ITagService tagService = A.Fake(); private readonly IAssetRepository assetRepository = A.Fake(); private readonly IAppEntity app = A.Fake(); - private readonly Guid appId = Guid.NewGuid(); - private readonly string appName = "my-app"; + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly QueryContext context; private readonly AssetQueryService sut; @@ -36,13 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets { var user = new ClaimsPrincipal(identity); - A.CallTo(() => app.Id).Returns(appId); - A.CallTo(() => app.Name).Returns(appName); + A.CallTo(() => app.Id).Returns(appId.Id); + A.CallTo(() => app.Name).Returns(appId.Name); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); context = QueryContext.Create(app, user); - A.CallTo(() => tagService.DenormalizeTagsAsync(appId, TagGroups.Assets, A>.That.IsSameSequenceAs("id1", "id2", "id3"))) + A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A>.That.IsSameSequenceAs("id1", "id2", "id3"))) .Returns(new Dictionary { ["id1"] = "name1", @@ -74,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets var ids = HashSet.Of(id1, id2); - A.CallTo(() => assetRepository.QueryAsync(appId, A>.That.IsSameSequenceAs(ids))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A>.That.IsSameSequenceAs(ids))) .Returns(ResultList.Create(8, CreateAsset(id1, "id1", "id2", "id3"), CreateAsset(id2))); @@ -91,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Assets [Fact] public async Task Should_load_assets_with_query_and_resolve_tags() { - A.CallTo(() => assetRepository.QueryAsync(appId, A.Ignored)) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.Ignored)) .Returns(ResultList.Create(8, CreateAsset(Guid.NewGuid(), "id1", "id2"), CreateAsset(Guid.NewGuid(), "id2", "id3"))); @@ -110,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World")); - A.CallTo(() => assetRepository.QueryAsync(appId, A.That.Matches(x => x.ToString() == "FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Matches(x => x.ToString() == "FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) .MustHaveHappened(); } @@ -119,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await sut.QueryAsync(context, Q.Empty.WithODataQuery("$filter=fileName eq 'ABC'")); - A.CallTo(() => assetRepository.QueryAsync(appId, A.That.Matches(x => x.ToString() == "Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Matches(x => x.ToString() == "Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) .MustHaveHappened(); } @@ -128,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=300&$skip=20")); - A.CallTo(() => assetRepository.QueryAsync(appId, A.That.Matches(x => x.ToString() == "Skip: 20; Take: 200; Sort: lastModified Descending"))) + A.CallTo(() => assetRepository.QueryAsync(appId.Id, A.That.Matches(x => x.ToString() == "Skip: 20; Take: 200; Sort: lastModified Descending"))) .MustHaveHappened(); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs index 01cf6bc52..a11765f7d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Backup { var @event = new MyEvent { - GuidNamed = new NamedId(RandomGuid(), $"name{i}"), + GuidNamed = NamedId.Of(RandomGuid(), $"name{i}"), GuidRaw = RandomGuid(), Values = new Dictionary { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs index 7ad1f700b..fd0557c23 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; using NodaTime; @@ -74,12 +75,21 @@ namespace Squidex.Domain.Apps.Entities.Contents public ContentGrainTests() { + var scripts = new Dictionary + { + [Scripts.Change] = "", + [Scripts.Create] = "", + [Scripts.Delete] = "", + [Scripts.Update] = "", + }; + var schemaDef = new Schema("my-schema") .AddNumber(1, "my-field1", Partitioning.Invariant, new NumberFieldProperties { IsRequired = true }) .AddNumber(2, "my-field2", Partitioning.Invariant, - new NumberFieldProperties { IsRequired = false }); + new NumberFieldProperties { IsRequired = false }) + .ConfigureScripts(scripts); A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); @@ -87,10 +97,6 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema)); A.CallTo(() => schema.SchemaDef).Returns(schemaDef); - A.CallTo(() => schema.ScriptCreate).Returns(""); - A.CallTo(() => schema.ScriptChange).Returns(""); - A.CallTo(() => schema.ScriptUpdate).Returns(""); - A.CallTo(() => schema.ScriptDelete).Returns(""); A.CallTo(() => scriptEngine.ExecuteAndTransform(A.Ignored, A.Ignored)) .ReturnsLazily(x => x.GetArgument(0).Data); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index e23488ba2..d4313212e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -42,12 +42,9 @@ namespace Squidex.Domain.Apps.Entities.Contents private readonly IAppEntity app = A.Fake(); private readonly IAppProvider appProvider = A.Fake(); private readonly IAssetUrlGenerator urlGenerator = A.Fake(); - private readonly Guid appId = Guid.NewGuid(); - private readonly Guid schemaId = Guid.NewGuid(); private readonly Guid contentId = Guid.NewGuid(); - private readonly string appName = "my-app"; - private readonly string schemaName = "my-schema"; - private readonly string script = ""; + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); + private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly NamedContentData contentData = new NamedContentData(); private readonly NamedContentData contentTransformed = new NamedContentData(); private readonly ClaimsPrincipal user; @@ -60,15 +57,17 @@ namespace Squidex.Domain.Apps.Entities.Contents { user = new ClaimsPrincipal(identity); - A.CallTo(() => app.Id).Returns(appId); - A.CallTo(() => app.Name).Returns(appName); + A.CallTo(() => app.Id).Returns(appId.Id); + A.CallTo(() => app.Name).Returns(appId.Name); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); - A.CallTo(() => schema.AppId).Returns(new NamedId(appId, appName)); - A.CallTo(() => schema.Id).Returns(schemaId); - A.CallTo(() => schema.Name).Returns(schemaName); - A.CallTo(() => schema.SchemaDef).Returns(new Schema(schemaName)); - A.CallTo(() => schema.ScriptQuery).Returns(script); + var schemaDef = + new Schema(schemaId.Name) + .ConfigureScripts(new Dictionary { [Scripts.Query] = "" }); + + A.CallTo(() => schema.Id).Returns(schemaId.Id); + A.CallTo(() => schema.AppId).Returns(appId); + A.CallTo(() => schema.SchemaDef).Returns(schemaDef); context = QueryContext.Create(app, user); @@ -86,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { SetupSchema(); - var result = await sut.GetSchemaAsync(context, schemaId.ToString()); + var result = await sut.GetSchemaAsync(context, schemaId.Name); Assert.Equal(schema, result); } @@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { SetupSchema(); - var result = await sut.GetSchemaAsync(context, schemaName); + var result = await sut.GetSchemaAsync(context, schemaId.Name); Assert.Equal(schema, result); } @@ -108,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - await Assert.ThrowsAsync(() => sut.GetSchemaAsync(ctx, schemaName)); + await Assert.ThrowsAsync(() => sut.GetSchemaAsync(ctx, schemaId.Name)); } [Fact] @@ -118,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - await Assert.ThrowsAsync(() => sut.ThrowIfSchemaNotExistsAsync(ctx, schemaName)); + await Assert.ThrowsAsync(() => sut.ThrowIfSchemaNotExistsAsync(ctx, schemaId.Name)); } public static IEnumerable SingleDataFrontend = new[] @@ -141,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - await Assert.ThrowsAsync(() => sut.FindContentAsync(ctx, schemaId.ToString(), contentId)); + await Assert.ThrowsAsync(() => sut.FindContentAsync(ctx, schemaId.Name, contentId)); } [Fact] @@ -155,7 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - await Assert.ThrowsAsync(async () => await sut.FindContentAsync(ctx, schemaId.ToString(), contentId)); + await Assert.ThrowsAsync(async () => await sut.FindContentAsync(ctx, schemaId.Name, contentId)); } [Theory] @@ -173,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithUnpublished(unpublished); - var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId); + var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId); Assert.Equal(contentTransformed, result.Data); Assert.Equal(content.Id, result.Id); @@ -197,7 +196,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithUnpublished(unpublished); - var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId); + var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId); Assert.Equal(contentTransformed, result.Data); Assert.Equal(content.Id, result.Id); @@ -220,7 +219,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId, 10); + var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId, 10); Assert.Equal(contentTransformed, result.Data); Assert.Equal(content.Id, result.Id); @@ -250,7 +249,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context; - await Assert.ThrowsAsync(() => sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty)); + await Assert.ThrowsAsync(() => sut.QueryAsync(ctx, schemaId.Name, Q.Empty)); } [Theory] @@ -268,7 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithArchived(archive).WithUnpublished(unpublished); - var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty); + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty); Assert.Equal(contentData, result[0].Data); Assert.Equal(content.Id, result[0].Id); @@ -294,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithArchived(archive).WithUnpublished(unpublished); - var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty); + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty); Assert.Equal(contentData, result[0].Data); Assert.Equal(contentId, result[0].Id); @@ -314,7 +313,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) .Throws(new ODataException()); - return Assert.ThrowsAsync(() => sut.QueryAsync(context, schemaId.ToString(), Q.Empty.WithODataQuery("query"))); + return Assert.ThrowsAsync(() => sut.QueryAsync(context, schemaId.Name, Q.Empty.WithODataQuery("query"))); } public static IEnumerable ManyIdDataFrontend = new[] @@ -348,7 +347,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithArchived(archive).WithUnpublished(unpublished); - var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty.WithIds(ids)); + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids)); Assert.Equal(ids, result.Select(x => x.Id).ToList()); Assert.Equal(total, result.Total); @@ -372,7 +371,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var ctx = context.WithArchived(archive).WithUnpublished(unpublished); - var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty.WithIds(ids)); + var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids)); Assert.Equal(ids, result.Select(x => x.Id).ToList()); Assert.Equal(total, result.Total); @@ -390,7 +389,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (allowSchema) { - identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.ForApp(Permissions.AppContentsRead, app.Name, schema.Name).Id)); + identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.ForApp(Permissions.AppContentsRead, app.Name, schema.SchemaDef.Name).Id)); } } @@ -398,7 +397,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { foreach (var id in ids) { - A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), script)) + A.CallTo(() => scriptEngine.Transform(A.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), "")) .Returns(contentTransformed); } } @@ -417,19 +416,19 @@ namespace Squidex.Domain.Apps.Entities.Contents private void SetupSchema() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) .Returns(schema); - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false)) .Returns(schema); } private void SetupSchemaNotFound() { - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) .Returns((ISchemaEntity)null); - A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false)) .Returns((ISchemaEntity)null); } @@ -441,7 +440,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => content.Data).Returns(contentData); A.CallTo(() => content.DataDraft).Returns(contentData); A.CallTo(() => content.Status).Returns(status); - A.CallTo(() => content.SchemaId).Returns(new NamedId(schemaId, schemaName)); + A.CallTo(() => content.SchemaId).Returns(schemaId); return content; } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index 7e1639b8e..41497e304 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -33,10 +33,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public class GraphQLTestBase { - protected static readonly Guid schemaId = Guid.NewGuid(); - protected static readonly Guid appId = Guid.NewGuid(); - protected static readonly string appName = "my-app"; protected readonly Schema schemaDef; + protected readonly Guid schemaId = Guid.NewGuid(); + protected readonly Guid appId = Guid.NewGuid(); + protected readonly string appName = "my-app"; protected readonly IContentQueryService contentQuery = A.Fake(); protected readonly IAssetQueryService assetQuery = A.Fake(); protected readonly ISchemaEntity schema = A.Fake(); @@ -76,7 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL new StringFieldProperties()) .AddArray(13, "my-array", Partitioning.Invariant, f => f .AddBoolean(121, "nested-boolean") - .AddNumber(122, "nested-number")); + .AddNumber(122, "nested-number")) + .ConfigureScripts(new Dictionary { [Scripts.Query] = "" }) + .Publish(); A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Name).Returns(appName); @@ -85,10 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL context = QueryContext.Create(app, user); A.CallTo(() => schema.Id).Returns(schemaId); - A.CallTo(() => schema.Name).Returns(schemaDef.Name); A.CallTo(() => schema.SchemaDef).Returns(schemaDef); - A.CallTo(() => schema.IsPublished).Returns(true); - A.CallTo(() => schema.ScriptQuery).Returns(""); var allSchemas = new List { schema }; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index 0ac1a3a41..5dda8794d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -8,6 +8,7 @@ using FakeItEasy; using NodaTime; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Guards; using Squidex.Domain.Apps.Entities.Schemas; @@ -25,6 +26,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanCreate_should_throw_exception_if_data_is_null() { + SetupSingleton(false); + var command = new CreateContent(); ValidationAssert.Throws(() => GuardContent.CanCreate(schema, command), @@ -34,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanCreate_should_throw_exception_if_singleton() { - A.CallTo(() => schema.IsSingleton).Returns(true); + SetupSingleton(true); var command = new CreateContent { Data = new NamedContentData() }; @@ -44,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id() { - A.CallTo(() => schema.IsSingleton).Returns(true); + SetupSingleton(true); var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id }; @@ -54,6 +57,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanCreate_should_not_throw_exception_if_data_is_not_null() { + SetupSingleton(false); + var command = new CreateContent { Data = new NamedContentData() }; GuardContent.CanCreate(schema, command); @@ -62,6 +67,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanUpdate_should_throw_exception_if_data_is_null() { + SetupSingleton(false); + var command = new UpdateContent(); ValidationAssert.Throws(() => GuardContent.CanUpdate(command), @@ -71,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanUpdate_should_not_throw_exception_if_data_is_not_null() { + SetupSingleton(false); + var command = new UpdateContent { Data = new NamedContentData() }; GuardContent.CanUpdate(command); @@ -79,6 +88,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanPatch_should_throw_exception_if_data_is_null() { + SetupSingleton(false); + var command = new PatchContent(); ValidationAssert.Throws(() => GuardContent.CanPatch(command), @@ -88,6 +99,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanPatch_should_not_throw_exception_if_data_is_not_null() { + SetupSingleton(false); + var command = new PatchContent { Data = new NamedContentData() }; GuardContent.CanPatch(command); @@ -96,6 +109,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_throw_exception_if_status_not_valid() { + SetupSingleton(false); + var command = new ChangeContentStatus { Status = (Status)10 }; ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command), @@ -105,6 +120,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_throw_exception_if_status_flow_not_valid() { + SetupSingleton(false); + var command = new ChangeContentStatus { Status = Status.Published }; ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command), @@ -114,6 +131,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past() { + SetupSingleton(false); + var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }; ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command), @@ -123,6 +142,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_throw_exception_if_publishing_without_pending_changes() { + SetupSingleton(false); + var command = new ChangeContentStatus { Status = Status.Published }; ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Published, command), @@ -132,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_throw_exception_if_singleton() { - A.CallTo(() => schema.IsSingleton).Returns(true); + SetupSingleton(true); var command = new ChangeContentStatus { Status = Status.Draft }; @@ -142,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_not_throw_exception_if_publishing_with_pending_changes() { - A.CallTo(() => schema.IsSingleton).Returns(true); + SetupSingleton(true); var command = new ChangeContentStatus { Status = Status.Published }; @@ -152,6 +173,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanChangeContentStatus_should_not_throw_exception_if_status_flow_valid() { + SetupSingleton(false); + var command = new ChangeContentStatus { Status = Status.Published }; GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command); @@ -168,6 +191,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanDiscardChanges_should_not_throw_exception_if_pending() { + SetupSingleton(false); + var command = new DiscardChanges(); GuardContent.CanDiscardChanges(true, command); @@ -176,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanDelete_should_throw_exception_if_singleton() { - A.CallTo(() => schema.IsSingleton).Returns(true); + SetupSingleton(true); var command = new DeleteContent(); @@ -186,9 +211,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard [Fact] public void CanDelete_should_not_throw_exception() { + SetupSingleton(false); + var command = new DeleteContent(); GuardContent.CanDelete(schema, command); } + + private void SetupSingleton(bool isSingleton) + { + A.CallTo(() => schema.SchemaDef) + .Returns(new Schema("schema", isSingleton: isSingleton)); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs index 110c0faba..cacfbe1a4 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content) { - return $"contents/{schema.Name}/{content.Id}"; + return $"contents/{schema.SchemaDef.Name}/{content.Id}"; } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 102a83295..101b9ce3a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -64,9 +64,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "invalid name", Properties = new StringFieldProperties(), @@ -87,9 +87,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = null, @@ -110,9 +110,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 }, @@ -134,9 +134,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = new StringFieldProperties(), @@ -157,15 +157,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = new StringFieldProperties(), Partitioning = Partitioning.Invariant.Key }, - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = new StringFieldProperties(), @@ -186,16 +186,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "array", Properties = new ArrayFieldProperties(), Partitioning = Partitioning.Invariant.Key, - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "invalid name", Properties = new StringFieldProperties() @@ -217,16 +217,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "array", Properties = new ArrayFieldProperties(), Partitioning = Partitioning.Invariant.Key, - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = null @@ -248,16 +248,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "array", Properties = new ArrayFieldProperties(), Partitioning = Partitioning.Invariant.Key, - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = new ArrayFieldProperties() @@ -279,16 +279,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "array", Properties = new ArrayFieldProperties(), Partitioning = Partitioning.Invariant.Key, - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 } @@ -311,21 +311,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "array", Properties = new ArrayFieldProperties(), Partitioning = Partitioning.Invariant.Key, - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = new StringFieldProperties() }, - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = new StringFieldProperties() @@ -347,33 +347,33 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new CreateSchema { AppId = appId, - Fields = new List + Fields = new List { - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = ValidProperties(), Partitioning = "invariant" }, - new CreateSchemaField + new UpsertSchemaField { Name = "field2", Properties = ValidProperties(), Partitioning = "invariant" }, - new CreateSchemaField + new UpsertSchemaField { Name = "field3", Properties = new ArrayFieldProperties(), Partitioning = "invariant", - Nested = new List + Nested = new List { - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, - new CreateSchemaNestedField + new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs index 5ee8bb093..4e3889439 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; +using Squidex.Infrastructure; using Squidex.Infrastructure.States; using Xunit; @@ -18,30 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes { private readonly IStore store = A.Fake>(); private readonly IPersistence persistence = A.Fake>(); - private readonly Guid appId = Guid.NewGuid(); - private readonly Guid schemaId1 = Guid.NewGuid(); - private readonly Guid schemaId2 = Guid.NewGuid(); - private readonly string schemaName1 = "my-schema1"; - private readonly string schemaName2 = "my-schema2"; + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); + private readonly NamedId schemaId1 = NamedId.Of(Guid.NewGuid(), "my-schema1"); + private readonly NamedId schemaId2 = NamedId.Of(Guid.NewGuid(), "my-schema2"); private readonly SchemasByAppIndexGrain sut; public SchemasByAppIndexGrainTests() { - A.CallTo(() => store.WithSnapshots(typeof(SchemasByAppIndexGrain), appId, A>.Ignored)) + A.CallTo(() => store.WithSnapshots(typeof(SchemasByAppIndexGrain), appId.Id, A>.Ignored)) .Returns(persistence); sut = new SchemasByAppIndexGrain(store); - sut.ActivateAsync(appId).Wait(); + sut.ActivateAsync(appId.Id).Wait(); } [Fact] public async Task Should_add_schema_id_to_index() { - await sut.AddSchemaAsync(schemaId1, schemaName1); + await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); - var result = await sut.GetSchemaIdAsync(schemaName1); + var result = await sut.GetSchemaIdAsync(schemaId1.Name); - Assert.Equal(schemaId1, result); + Assert.Equal(schemaId1.Id, result); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .MustHaveHappened(); @@ -50,10 +49,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes [Fact] public async Task Should_delete_and_reset_state_when_cleaning() { - await sut.AddSchemaAsync(schemaId1, schemaName1); + await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); await sut.ClearAsync(); - var id = await sut.GetSchemaIdAsync(schemaName1); + var id = await sut.GetSchemaIdAsync(schemaId1.Name); Assert.Equal(id, Guid.Empty); @@ -64,10 +63,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes [Fact] public async Task Should_remove_schema_id_from_index() { - await sut.AddSchemaAsync(schemaId1, schemaName1); - await sut.RemoveSchemaAsync(schemaId1); + await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name); + await sut.RemoveSchemaAsync(schemaId1.Id); - var result = await sut.GetSchemaIdAsync(schemaName1); + var result = await sut.GetSchemaIdAsync(schemaId1.Name); Assert.Equal(Guid.Empty, result); @@ -80,16 +79,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes { var state = new Dictionary { - [schemaName1] = schemaId1, - [schemaName2] = schemaId2 + [schemaId1.Name] = schemaId1.Id, + [schemaId2.Name] = schemaId2.Id }; await sut.RebuildAsync(state); - Assert.Equal(schemaId1, await sut.GetSchemaIdAsync(schemaName1)); - Assert.Equal(schemaId2, await sut.GetSchemaIdAsync(schemaName2)); + Assert.Equal(schemaId1.Id, await sut.GetSchemaIdAsync(schemaId1.Name)); + Assert.Equal(schemaId2.Id, await sut.GetSchemaIdAsync(schemaId2.Name)); - Assert.Equal(new List { schemaId1, schemaId2 }, await sut.GetSchemaIdsAsync()); + Assert.Equal(new List { schemaId1.Id, schemaId2.Id }, await sut.GetSchemaIdsAsync()); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .MustHaveHappened(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs index 6f7667508..5e88d8bae 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs @@ -26,7 +26,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas public class SchemaGrainTests : HandlerTestBase { private readonly IAppProvider appProvider = A.Fake(); - private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry()); private readonly string fieldName = "age"; private readonly string arrayName = "array"; private readonly NamedId fieldId = NamedId.Of(1L, "age"); @@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas A.CallTo(() => appProvider.GetSchemaAsync(AppId, SchemaName)) .Returns((ISchemaEntity)null); - sut = new SchemaGrain(Store, A.Dummy(), appProvider, registry); + sut = new SchemaGrain(Store, A.Dummy(), appProvider, TestUtils.DefaultSerializer); sut.ActivateAsync(Id).Wait(); } @@ -70,13 +69,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas Assert.Equal(AppId, sut.Snapshot.AppId.Id); - Assert.Equal(SchemaName, sut.Snapshot.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); - Assert.True(sut.Snapshot.IsSingleton); + Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); + Assert.True(sut.Snapshot.SchemaDef.IsSingleton); LastEvents .ShouldHaveSameEvents( - CreateEvent(new SchemaCreated { Name = SchemaName, Properties = properties, Singleton = true }) + CreateEvent(new SchemaCreated { Schema = new Schema(command.Name, command.Properties, command.Singleton) }) ); } @@ -85,19 +84,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas { var properties = new SchemaProperties(); - var fields = new List + var fields = new List { - new CreateSchemaField { Name = "field1", Properties = ValidProperties() }, - new CreateSchemaField { Name = "field2", Properties = ValidProperties() }, - new CreateSchemaField + new UpsertSchemaField { Name = "field1", Properties = ValidProperties() }, + new UpsertSchemaField { Name = "field2", Properties = ValidProperties() }, + new UpsertSchemaField { Name = "field3", Partitioning = Partitioning.Language.Key, Properties = new ArrayFieldProperties(), - Nested = new List + Nested = new List { - new CreateSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, - new CreateSchemaNestedField { Name = "nested2", Properties = ValidProperties() } + new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, + new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() } } } }; @@ -111,10 +110,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas var @event = (SchemaCreated)LastEvents.Single().Payload; Assert.Equal(AppId, sut.Snapshot.AppId.Id); - Assert.Equal(SchemaName, sut.Snapshot.Name); + Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); - Assert.Equal(3, @event.Fields.Count); + Assert.Equal(3, @event.Schema.Fields.Count); } [Fact] @@ -141,11 +140,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas { var command = new ConfigureScripts { - ScriptQuery = "", - ScriptCreate = "", - ScriptUpdate = "", - ScriptDelete = "", - ScriptChange = "" + Scripts = new Dictionary + { + ["Query"] = "" + } }; await ExecuteCreateAsync(); @@ -156,14 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas LastEvents .ShouldHaveSameEvents( - CreateEvent(new ScriptsConfigured - { - ScriptQuery = "", - ScriptCreate = "", - ScriptUpdate = "", - ScriptDelete = "", - ScriptChange = "" - }) + CreateEvent(new SchemaScriptsConfigured { Scripts = command.Scripts }) ); } @@ -217,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas result.ShouldBeEquivalent(new EntitySavedResult(1)); - Assert.Equal(command.Name, sut.Snapshot.Category); + Assert.Equal(command.Name, sut.Snapshot.SchemaDef.Category); LastEvents .ShouldHaveSameEvents( @@ -242,7 +233,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas result.ShouldBeEquivalent(new EntitySavedResult(1)); - Assert.Equal(command.PreviewUrls, sut.Snapshot.PreviewUrls); + Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls); LastEvents .ShouldHaveSameEvents( diff --git a/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs b/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs index 64704a44b..1d19f9baf 100644 --- a/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs @@ -22,6 +22,7 @@ namespace Squidex.Pipeline.CommandMiddlewares { private readonly IHttpContextAccessor httpContextAccessor = A.Fake(); private readonly ICommandBus commandBus = A.Fake(); + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly HttpContext httpContext = new DefaultHttpContext(); private readonly EnrichWithAppIdCommandMiddleware sut; @@ -30,12 +31,21 @@ namespace Squidex.Pipeline.CommandMiddlewares A.CallTo(() => httpContextAccessor.HttpContext) .Returns(httpContext); + var appEntity = A.Fake(); + + A.CallTo(() => appEntity.Id).Returns(appId.Id); + A.CallTo(() => appEntity.Name).Returns(appId.Name); + + httpContext.Features.Set(new AppResolver.AppFeature(appEntity)); + sut = new EnrichWithAppIdCommandMiddleware(httpContextAccessor); } [Fact] public async Task Should_throw_exception_if_app_not_found() { + httpContext.Features.Set(new AppResolver.AppFeature(null)); + var command = new CreateContent(); var context = new CommandContext(command, commandBus); @@ -59,65 +69,45 @@ namespace Squidex.Pipeline.CommandMiddlewares [Fact] public async Task Should_assign_app_id_and_name_to_app_command() { - SetupApp(out var appId, out var appName); - var command = new CreateContent(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(NamedId.Of(appId, appName), command.AppId); + Assert.Equal(appId, command.AppId); } [Fact] public async Task Should_assign_app_id_to_app_self_command() { - SetupApp(out var appId, out _); - var command = new AddPattern(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(appId, command.AppId); + Assert.Equal(appId.Id, command.AppId); } [Fact] public async Task Should_not_override_app_id() { - SetupApp(out var appId, out _); - var command = new AddPattern { AppId = Guid.NewGuid() }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.NotEqual(appId, command.AppId); + Assert.NotEqual(appId.Id, command.AppId); } [Fact] public async Task Should_not_override_app_id_and_name() { - SetupApp(out var appId, out var appName); - var command = new CreateContent { AppId = NamedId.Of(Guid.NewGuid(), "other-app") }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.NotEqual(NamedId.Of(appId, appName), command.AppId); - } - - private void SetupApp(out Guid appId, out string appName) - { - appId = Guid.NewGuid(); - appName = "my-app"; - - var appEntity = A.Fake(); - A.CallTo(() => appEntity.Id).Returns(appId); - A.CallTo(() => appEntity.Name).Returns(appName); - - httpContext.Features.Set(new AppResolver.AppFeature(appEntity)); + Assert.NotEqual(appId, command.AppId); } } } diff --git a/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs b/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs index 3af7df6a2..6e2e99e9c 100644 --- a/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Commands; @@ -28,6 +29,8 @@ namespace Squidex.Pipeline.CommandMiddlewares private readonly IActionContextAccessor actionContextAccessor = A.Fake(); private readonly IAppProvider appProvider = A.Fake(); private readonly ICommandBus commandBus = A.Fake(); + private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); + private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly HttpContext httpContext = new DefaultHttpContext(); private readonly ActionContext actionContext = new ActionContext(); private readonly EnrichWithSchemaIdCommandMiddleware sut; @@ -40,16 +43,30 @@ namespace Squidex.Pipeline.CommandMiddlewares A.CallTo(() => actionContextAccessor.ActionContext) .Returns(actionContext); + var appEntity = A.Fake(); + + A.CallTo(() => appEntity.Id).Returns(appId.Id); + A.CallTo(() => appEntity.Name).Returns(appId.Name); + + httpContext.Features.Set(new AppResolver.AppFeature(appEntity)); + + var schemaEntity = A.Fake(); + + A.CallTo(() => schemaEntity.Id).Returns(schemaId.Id); + A.CallTo(() => schemaEntity.SchemaDef).Returns(new Schema(schemaId.Name)); + + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name)) + .Returns(schemaEntity); + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false)) + .Returns(schemaEntity); + sut = new EnrichWithSchemaIdCommandMiddleware(appProvider, actionContextAccessor); } [Fact] public async Task Should_throw_exception_if_app_not_found() { - SetupApp(out var appId, out _); - SetupSchema(appId, out _, out _); - - A.CallTo(() => appProvider.GetSchemaAsync(appId, "other-schema")) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, "other-schema")) .Returns(Task.FromResult(null)); actionContext.RouteData.Values["name"] = "other-schema"; @@ -77,9 +94,6 @@ namespace Squidex.Pipeline.CommandMiddlewares [Fact] public async Task Should_do_nothing_when_route_has_no_parameter() { - SetupApp(out var appId, out _); - SetupSchema(appId, out _, out _); - var command = new CreateContent(); var context = new CommandContext(command, commandBus); @@ -91,124 +105,75 @@ namespace Squidex.Pipeline.CommandMiddlewares [Fact] public async Task Should_assign_schema_id_and_name_from_name() { - SetupApp(out var appId, out _); - SetupSchema(appId, out var schemaId, out var schemaName); - - actionContext.RouteData.Values["name"] = schemaName; + actionContext.RouteData.Values["name"] = schemaId.Name; var command = new CreateContent(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); + Assert.Equal(schemaId, command.SchemaId); } [Fact] public async Task Should_assign_schema_id_and_name_from_id() { - SetupApp(out var appId, out _); - SetupSchema(appId, out var schemaId, out var schemaName); - - actionContext.RouteData.Values["name"] = schemaId.ToString(); + actionContext.RouteData.Values["name"] = schemaId.Name; var command = new CreateContent(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); + Assert.Equal(schemaId, command.SchemaId); } [Fact] public async Task Should_assign_schema_id_from_id() { - SetupApp(out var appId, out _); - SetupSchema(appId, out var schemaId, out _); - - actionContext.RouteData.Values["name"] = schemaId.ToString(); + actionContext.RouteData.Values["name"] = schemaId.Name; var command = new UpdateSchema(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(schemaId, command.SchemaId); + Assert.Equal(schemaId.Id, command.SchemaId); } [Fact] public async Task Should_use_app_id_from_command() { - var appId = NamedId.Of(Guid.NewGuid(), "my-app"); - - SetupSchema(appId.Id, out var schemaId, out var schemaName); - - actionContext.RouteData.Values["name"] = schemaId.ToString(); + actionContext.RouteData.Values["name"] = schemaId.Name; var command = new CreateContent { AppId = appId }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); + Assert.Equal(schemaId, command.SchemaId); } [Fact] public async Task Should_not_override_schema_id() { - SetupApp(out var appId, out _); - SetupSchema(appId, out var schemaId, out _); - var command = new CreateSchema { SchemaId = Guid.NewGuid() }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.NotEqual(schemaId, command.SchemaId); + Assert.NotEqual(schemaId.Id, command.SchemaId); } [Fact] public async Task Should_not_override_schema_id_and_name() { - SetupApp(out var appId, out var appName); - SetupSchema(appId, out _, out _); - var command = new CreateContent { SchemaId = NamedId.Of(Guid.NewGuid(), "other-schema") }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); - Assert.NotEqual(NamedId.Of(appId, appName), command.AppId); - } - - private void SetupSchema(Guid appId, out Guid schemaId, out string schemaName) - { - schemaId = Guid.NewGuid(); - schemaName = "my-schema"; - - var schemaEntity = A.Fake(); - A.CallTo(() => schemaEntity.Id).Returns(schemaId); - A.CallTo(() => schemaEntity.Name).Returns(schemaName); - - var temp1 = schemaName; - var temp2 = schemaId; - - A.CallTo(() => appProvider.GetSchemaAsync(appId, temp1)) - .Returns(schemaEntity); - A.CallTo(() => appProvider.GetSchemaAsync(appId, temp2, false)) - .Returns(schemaEntity); - } - - private void SetupApp(out Guid appId, out string appName) - { - appId = Guid.NewGuid(); - appName = "my-app"; - - var appEntity = A.Fake(); - A.CallTo(() => appEntity.Id).Returns(appId); - A.CallTo(() => appEntity.Name).Returns(appName); - - httpContext.Features.Set(new AppResolver.AppFeature(appEntity)); + Assert.NotEqual(appId, command.AppId); } } } diff --git a/tools/Migrate_01/Migrations/PopulateGrainIndexes.cs b/tools/Migrate_01/Migrations/PopulateGrainIndexes.cs index 24ed5e092..d37d03b91 100644 --- a/tools/Migrate_01/Migrations/PopulateGrainIndexes.cs +++ b/tools/Migrate_01/Migrations/PopulateGrainIndexes.cs @@ -106,7 +106,7 @@ namespace Migrate_01.Migrations { if (!schema.IsDeleted) { - schemasByApp.GetOrAddNew(schema.AppId.Id)[schema.Name] = schema.Id; + schemasByApp.GetOrAddNew(schema.AppId.Id)[schema.SchemaDef.Name] = schema.Id; } return TaskHelper.Done; diff --git a/tools/Migrate_01/OldEvents/SchemaCreated.cs b/tools/Migrate_01/OldEvents/SchemaCreated.cs new file mode 100644 index 000000000..2c572b322 --- /dev/null +++ b/tools/Migrate_01/OldEvents/SchemaCreated.cs @@ -0,0 +1,106 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; +using SchemaCreatedV2 = Squidex.Domain.Apps.Events.Schemas.SchemaCreated; +using SchemaFields = System.Collections.Generic.List; + +namespace Migrate_01.OldEvents +{ + [EventType(nameof(SchemaCreated))] + [Obsolete] + public sealed class SchemaCreated : SchemaEvent, IMigrated + { + public string Name { get; set; } + + public bool Singleton { get; set; } + + public bool Publish { get; set; } + + public SchemaFields Fields { get; set; } + + public SchemaProperties Properties { get; set; } + + public IEvent Migrate() + { + var schema = new Schema(Name, Properties, Singleton); + + if (Publish) + { + schema = schema.Publish(); + } + + var totalFields = 0; + + if (Fields != null) + { + foreach (var eventField in Fields) + { + totalFields++; + + var partitioning = Partitioning.FromString(eventField.Partitioning); + + var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning); + + if (field is ArrayField arrayField && eventField.Nested?.Count > 0) + { + foreach (var nestedEventField in eventField.Nested) + { + totalFields++; + + var nestedField = nestedEventField.Properties.CreateNestedField(totalFields, nestedEventField.Name); + + if (nestedEventField.IsHidden) + { + nestedField = nestedField.Hide(); + } + + if (nestedEventField.IsDisabled) + { + nestedField = nestedField.Disable(); + } + + if (nestedEventField.IsLocked) + { + nestedField = nestedField.Lock(); + } + + arrayField = arrayField.AddField(nestedField); + } + + field = arrayField; + } + + if (eventField.IsHidden) + { + field = field.Hide(); + } + + if (eventField.IsDisabled) + { + field = field.Disable(); + } + + if (eventField.IsLocked) + { + field = field.Lock(); + } + + schema = schema.AddField(field); + } + } + + return SimpleMapper.Map(this, new SchemaCreatedV2 { Schema = schema }); + } + } +} diff --git a/tools/Migrate_01/OldEvents/ScriptsConfigured.cs b/tools/Migrate_01/OldEvents/ScriptsConfigured.cs new file mode 100644 index 000000000..31624535e --- /dev/null +++ b/tools/Migrate_01/OldEvents/ScriptsConfigured.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; + +namespace Migrate_01.OldEvents +{ + [EventType(nameof(ScriptsConfigured))] + [Obsolete] + public sealed class ScriptsConfigured : SchemaEvent, IMigrated + { + public string ScriptQuery { get; set; } + + public string ScriptCreate { get; set; } + + public string ScriptUpdate { get; set; } + + public string ScriptDelete { get; set; } + + public string ScriptChange { get; set; } + + public IEvent Migrate() + { + var scripts = new Dictionary(); + + if (!string.IsNullOrWhiteSpace(ScriptQuery)) + { + scripts[Scripts.Query] = ScriptQuery; + } + + if (!string.IsNullOrWhiteSpace(ScriptCreate)) + { + scripts[Scripts.Create] = ScriptCreate; + } + + if (!string.IsNullOrWhiteSpace(ScriptUpdate)) + { + scripts[Scripts.Update] = ScriptUpdate; + } + + if (!string.IsNullOrWhiteSpace(ScriptDelete)) + { + scripts[Scripts.Delete] = ScriptDelete; + } + + if (!string.IsNullOrWhiteSpace(ScriptChange)) + { + scripts[Scripts.Change] = ScriptChange; + } + + return SimpleMapper.Map(this, new SchemaScriptsConfigured { Scripts = scripts }); + } + } +} diff --git a/tools/Migrate_01/Rebuilder.cs b/tools/Migrate_01/Rebuilder.cs index 39fac9933..f29f924c2 100644 --- a/tools/Migrate_01/Rebuilder.cs +++ b/tools/Migrate_01/Rebuilder.cs @@ -31,18 +31,15 @@ namespace Migrate_01 { public sealed class Rebuilder { - private readonly FieldRegistry fieldRegistry; private readonly ILocalCache localCache; private readonly IStore store; private readonly IEventStore eventStore; public Rebuilder( - FieldRegistry fieldRegistry, ILocalCache localCache, IStore store, IEventStore eventStore) { - this.fieldRegistry = fieldRegistry; this.eventStore = eventStore; this.localCache = localCache; this.store = store; @@ -59,7 +56,7 @@ namespace Migrate_01 { await store.GetSnapshotStore().ClearAsync(); - await RebuildManyAsync("^schema\\-", id => RebuildAsync(id, (e, s) => s.Apply(e, fieldRegistry))); + await RebuildManyAsync("^schema\\-", id => RebuildAsync(id, (e, s) => s.Apply(e))); } public async Task RebuildRulesAsync() From c41e69c38770d072d776077d51641fd5ecec5d96 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 26 Jan 2019 23:39:37 +0100 Subject: [PATCH 3/7] Tests for sync. --- .../Schemas/Json/JsonNestedFieldModel.cs | 8 +- .../Schemas/Json/JsonSchemaModel.cs | 41 +++++++-- .../SchemaSynchronizer.cs | 22 ++--- .../Schemas/Commands/SynchronizeSchema.cs | 3 + .../Schemas/Commands/UpsertCommand.cs | 15 ++++ .../Schemas/Commands/UpsertSchemaField.cs | 3 +- .../Schemas/SchemaGrain.cs | 27 ++++-- .../Schemas/Models/ConfigureScriptsDto.cs | 31 +------ .../Schemas/Models/CreateSchemaDto.cs | 59 +------------ .../Schemas/Models/SynchronizeSchemaDto.cs | 29 +++++++ .../Controllers/Schemas/Models/UpsertDto.cs | 87 +++++++++++++++++++ ...emaFieldDto.cs => UpsertSchemaFieldDto.cs} | 4 +- ...ldDto.cs => UpsertSchemaNestedFieldDto.cs} | 2 +- .../Controllers/Schemas/SchemasController.cs | 22 +++++ .../Model/Schemas/SchemaTests.cs | 13 ++- .../TestUtils.cs | 4 +- .../Schemas/Guards/GuardSchemaTests.cs | 6 +- .../Schemas/SchemaGrainTests.cs | 42 ++++++++- tools/Migrate_01/Rebuilder.cs | 1 - 19 files changed, 295 insertions(+), 124 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Schemas/Models/SynchronizeSchemaDto.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs rename src/Squidex/Areas/Api/Controllers/Schemas/Models/{CreateSchemaFieldDto.cs => UpsertSchemaFieldDto.cs} (93%) rename src/Squidex/Areas/Api/Controllers/Schemas/Models/{CreateSchemaNestedFieldDto.cs => UpsertSchemaNestedFieldDto.cs} (96%) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs index c7a42baaa..21f28db97 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs @@ -20,17 +20,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public bool IsHidden { get; set; } + [JsonProperty] + public bool IsLocked { get; set; } + [JsonProperty] public bool IsDisabled { get; set; } [JsonProperty] public FieldProperties Properties { get; set; } - public bool IsLocked - { - get { return false; } - } - public NestedField ToNestedField() { return Properties.CreateNestedField(Id, Name, this); diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs index c3c04c851..bfbf2c709 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs @@ -6,8 +6,11 @@ // ========================================================================== using System; +using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Squidex.Infrastructure; +using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Core.Schemas.Json { @@ -16,6 +19,12 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public string Name { get; set; } + [JsonProperty] + public string Category { get; set; } + + [JsonProperty] + public bool IsSingleton { get; set; } + [JsonProperty] public bool IsPublished { get; set; } @@ -25,15 +34,19 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public JsonFieldModel[] Fields { get; set; } + [JsonProperty] + public IReadOnlyDictionary Scripts { get; set; } + + [JsonProperty] + public IReadOnlyDictionary PreviewUrls { get; set; } + public JsonSchemaModel() { } public JsonSchemaModel(Schema schema) { - Name = schema.Name; - - Properties = schema.Properties; + SimpleMapper.Map(schema, this); Fields = schema.Fields.ToArray(x => @@ -48,8 +61,6 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json Partitioning = x.Partitioning.Key, Properties = x.RawProperties }); - - IsPublished = schema.IsPublished; } private static JsonNestedFieldModel[] CreateChildren(IField field) @@ -62,6 +73,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json Id = x.Id, Name = x.Name, IsHidden = x.IsHidden, + IsLocked = x.IsLocked, IsDisabled = x.IsDisabled, Properties = x.RawProperties }); @@ -74,7 +86,24 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json { var fields = Fields.ToArray(f => f.ToField()) ?? Array.Empty(); - return new Schema(Name, fields, Properties, IsPublished); + var schema = new Schema(Name, fields, Properties, IsPublished, IsSingleton); + + if (!string.IsNullOrWhiteSpace(Category)) + { + schema = schema.ChangeCategory(Category); + } + + if (Scripts?.Count > 0) + { + schema = schema.ConfigureScripts(Scripts); + } + + if (PreviewUrls?.Count > 0) + { + schema = schema.ConfigurePreviewUrls(PreviewUrls); + } + + return schema; } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs index 5622b4807..cc693745c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs @@ -38,13 +38,6 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization return @event; } - var events = SyncFields(source.FieldCollection, target.FieldCollection, serializer, idGenerator, null, options); - - foreach (var @event in events) - { - yield return E(@event); - } - if (!source.Properties.EqualsJson(target.Properties, serializer)) { yield return E(new SchemaUpdated { Properties = target.Properties }); @@ -55,14 +48,14 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization yield return E(new SchemaCategoryChanged { Name = target.Category }); } - if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls)) + if (!source.Scripts.EqualsDictionary(target.Scripts)) { - yield return E(new SchemaPreviewUrlsConfigured { PreviewUrls = target.PreviewUrls.ToDictionary(x => x.Key, x => x.Value) }); + yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts.ToDictionary(x => x.Key, x => x.Value) }); } - if (!source.Scripts.EqualsDictionary(target.Scripts)) + if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls)) { - yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts.ToDictionary(x => x.Key, x => x.Value) }); + yield return E(new SchemaPreviewUrlsConfigured { PreviewUrls = target.PreviewUrls.ToDictionary(x => x.Key, x => x.Value) }); } if (source.IsPublished != target.IsPublished) @@ -71,6 +64,13 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization E(new SchemaPublished()) : E(new SchemaUnpublished()); } + + var events = SyncFields(source.FieldCollection, target.FieldCollection, serializer, idGenerator, null, options); + + foreach (var @event in events) + { + yield return E(@event); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs index 97b093dd5..cc185e52b 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs @@ -9,5 +9,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class SynchronizeSchema : UpsertCommand { + public bool NoFieldDeletion { get; set; } + + public bool NoFieldRecreation { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs index 8e4b18427..0ab6fa7cf 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs @@ -35,6 +35,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands schema = schema.Publish(); } + if (Scripts != null) + { + schema = schema.ConfigureScripts(Scripts); + } + + if (PreviewUrls != null) + { + schema = schema.ConfigurePreviewUrls(PreviewUrls); + } + + if (!string.IsNullOrWhiteSpace(Category)) + { + schema = schema.ChangeCategory(Category); + } + var totalFields = 0; if (Fields != null) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs index cc92d2e8f..3b6184338 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs @@ -6,12 +6,13 @@ // ========================================================================== using System.Collections.Generic; +using P = Squidex.Domain.Apps.Core.Partitioning; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class UpsertSchemaField : UpsertSchemaFieldBase { - public string Partitioning { get; set; } = "invariant"; + public string Partitioning { get; set; } = P.Invariant.Key; public List Nested { get; set; } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 86e923b6d..70ec38ddc 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -76,6 +76,14 @@ namespace Squidex.Domain.Apps.Entities.Schemas Create(c); }); + case SynchronizeSchema synchronizeSchema: + return UpdateAsync(synchronizeSchema, c => + { + GuardSchema.CanSynchronize(c); + + Synchronize(c); + }); + case DeleteField deleteField: return UpdateAsync(deleteField, c => { @@ -203,14 +211,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas public void Synchronize(SynchronizeSchema command) { + var options = new SchemaSynchronizationOptions + { + NoFieldDeletion = command.NoFieldDeletion, + NoFieldRecreation = command.NoFieldRecreation + }; + var schemaSource = Snapshot.SchemaDef; var schemaTarget = command.ToSchema(schemaSource.Name, schemaSource.IsSingleton); - var @events = schemaTarget.Synchronize(schemaSource, serializer, () => Snapshot.SchemaFieldsTotal + 1); + var @events = schemaSource.Synchronize(schemaTarget, serializer, () => Snapshot.SchemaFieldsTotal + 1, options); foreach (var @event in @events) { - RaiseEvent(@event); + RaiseEvent(SimpleMapper.Map(command, (SchemaEvent)@event)); } } @@ -221,7 +235,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas public void Add(AddField command) { - RaiseEvent(command, new FieldAdded { FieldId = NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name) }); + RaiseEvent(command, new FieldAdded { FieldId = CreateFieldId(command) }); } public void UpdateField(UpdateField command) @@ -321,9 +335,12 @@ namespace Squidex.Domain.Apps.Entities.Schemas { pe.ParentFieldId = NamedId.Of(field.Id, field.Name); - if (field is IArrayField arrayField && command is FieldCommand fc && @event is FieldEvent fe && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField)) + if (command is FieldCommand fc && @event is FieldEvent fe) { - fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); + if (field is IArrayField arrayField && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField)) + { + fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); + } } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs index 51c8c3de1..8247b7179 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs @@ -5,41 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using Squidex.Domain.Apps.Entities.Schemas.Commands; -using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class ConfigureScriptsDto + public sealed class ConfigureScriptsDto : Dictionary { - /// - /// The script that is executed for each query when querying contents. - /// - public string ScriptQuery { get; set; } - - /// - /// The script that is executed when creating a content. - /// - public string ScriptCreate { get; set; } - - /// - /// The script that is executed when updating a content. - /// - public string ScriptUpdate { get; set; } - - /// - /// The script that is executed when deleting a content. - /// - public string ScriptDelete { get; set; } - - /// - /// The script that is executed when change a content status. - /// - public string ScriptChange { get; set; } - public ConfigureScripts ToCommand() { - return SimpleMapper.Map(this, new ConfigureScripts()); + return new ConfigureScripts { Scripts = this }; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs index b18eb7d62..a2b8e3299 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs @@ -5,15 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; -using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class CreateSchemaDto + public sealed class CreateSchemaDto : UpsertDto { /// /// The name of the schema. @@ -22,66 +19,14 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] public string Name { get; set; } - /// - /// The optional properties. - /// - public SchemaPropertiesDto Properties { get; set; } - - /// - /// Optional fields. - /// - public List Fields { get; set; } - /// /// Set to true to allow a single content item only. /// public bool Singleton { get; set; } - /// - /// Set it to true to autopublish the schema. - /// - public bool Publish { get; set; } - public CreateSchema ToCommand() { - var command = new CreateSchema(); - - SimpleMapper.Map(this, command); - - if (Properties != null) - { - command.Properties = new SchemaProperties(); - - SimpleMapper.Map(Properties, command.Properties); - } - - if (Fields != null) - { - command.Fields = new List(); - - foreach (var fieldDto in Fields) - { - var rootProperties = fieldDto?.Properties.ToProperties(); - var rootField = SimpleMapper.Map(fieldDto, new UpsertSchemaField { Properties = rootProperties }); - - if (fieldDto.Nested != null) - { - rootField.Nested = new List(); - - foreach (var nestedFieldDto in fieldDto.Nested) - { - var nestedProperties = nestedFieldDto?.Properties.ToProperties(); - var nestedField = SimpleMapper.Map(nestedFieldDto, new UpsertSchemaNestedField { Properties = nestedProperties }); - - rootField.Nested.Add(nestedField); - } - } - - command.Fields.Add(rootField); - } - } - - return command; + return ToCommand(this, new CreateSchema()); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SynchronizeSchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SynchronizeSchemaDto.cs new file mode 100644 index 000000000..e2595140a --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SynchronizeSchemaDto.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Entities.Schemas.Commands; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public sealed class SynchronizeSchemaDto : UpsertDto + { + /// + /// True, when fields should not be deleted. + /// + public bool NoFieldDeletion { get; set; } + + /// + /// True, when fields with different types should not be recreated. + /// + public bool NoFieldRecreation { get; set; } + + public SynchronizeSchema ToCommand() + { + return ToCommand(this, new SynchronizeSchema()); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs new file mode 100644 index 000000000..0e199c6cf --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs @@ -0,0 +1,87 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public abstract class UpsertDto + { + /// + /// The optional properties. + /// + public SchemaPropertiesDto Properties { get; set; } + + /// + /// Optional fields. + /// + public List Fields { get; set; } + + /// + /// The optional scripts. + /// + public Dictionary Scripts { get; set; } + + /// + /// The optional preview urls. + /// + public Dictionary PreviewUrls { get; set; } + + /// + /// The category. + /// + public string Category { get; set; } + + /// + /// Set it to true to autopublish the schema. + /// + public bool Publish { get; set; } + + public static TCommand ToCommand(TDto dto, TCommand command) where TCommand : UpsertCommand where TDto : UpsertDto + { + SimpleMapper.Map(dto, command); + + if (dto.Properties != null) + { + command.Properties = new SchemaProperties(); + + SimpleMapper.Map(dto.Properties, command.Properties); + } + + if (dto.Fields != null) + { + command.Fields = new List(); + + foreach (var fieldDto in dto.Fields) + { + var rootProperties = fieldDto?.Properties.ToProperties(); + var rootField = SimpleMapper.Map(fieldDto, new UpsertSchemaField { Properties = rootProperties }); + + if (fieldDto.Nested != null) + { + rootField.Nested = new List(); + + foreach (var nestedFieldDto in fieldDto.Nested) + { + var nestedProperties = nestedFieldDto?.Properties.ToProperties(); + var nestedField = SimpleMapper.Map(nestedFieldDto, new UpsertSchemaNestedField { Properties = nestedProperties }); + + rootField.Nested.Add(nestedField); + } + } + + command.Fields.Add(rootField); + } + } + + return command; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaFieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaFieldDto.cs similarity index 93% rename from src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaFieldDto.cs rename to src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaFieldDto.cs index 75a09a657..a42fee220 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaFieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaFieldDto.cs @@ -10,7 +10,7 @@ using System.ComponentModel.DataAnnotations; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class CreateSchemaFieldDto + public sealed class UpsertSchemaFieldDto { /// /// The name of the field. Must be unique within the schema. @@ -48,6 +48,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// /// The nested fields. /// - public List Nested { get; set; } + public List Nested { get; set; } } } \ No newline at end of file diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaNestedFieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaNestedFieldDto.cs similarity index 96% rename from src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaNestedFieldDto.cs rename to src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaNestedFieldDto.cs index 6f4855264..823bddd69 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaNestedFieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertSchemaNestedFieldDto.cs @@ -9,7 +9,7 @@ using System.ComponentModel.DataAnnotations; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class CreateSchemaNestedFieldDto + public sealed class UpsertSchemaNestedFieldDto { /// /// The name of the field. Must be unique within the schema. diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index d3f034d96..b867cdd2e 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -147,6 +147,28 @@ namespace Squidex.Areas.Api.Controllers.Schemas return NoContent(); } + /// + /// Synchronize a schema. + /// + /// The name of the app. + /// The name of the schema. + /// The schema object that needs to updated. + /// + /// 204 => Schema has been updated. + /// 400 => Schema properties are not valid. + /// 404 => Schema or app not found. + /// + [HttpPut] + [Route("apps/{app}/schemas/{name}/sync")] + [ApiPermission(Permissions.AppSchemasUpdate)] + [ApiCosts(1)] + public async Task PutSchemaSync(string app, string name, [FromBody] SynchronizeSchemaDto request) + { + await CommandBus.PublishAsync(request.ToCommand()); + + return NoContent(); + } + /// /// Update a schema category. /// diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index 4f426ee04..e8005557e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -278,7 +278,18 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas [Fact] public void Should_serialize_and_deserialize_schema() { - var schemaSource = TestUtils.MixedSchema(); + var schemaSource = + TestUtils.MixedSchema(true) + .ChangeCategory("Category") + .ConfigurePreviewUrls(new Dictionary + { + ["web"] = "Url" + }) + .ConfigureScripts(new Dictionary + { + ["create"] = "" + }); + var schemaTarget = schemaSource.SerializeAndDeserialize(); schemaTarget.Should().BeEquivalentTo(schemaSource); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs index dec0ade55..ef69a262a 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs @@ -59,9 +59,9 @@ namespace Squidex.Domain.Apps.Core return new NewtonsoftJsonSerializer(serializerSettings); } - public static Schema MixedSchema() + public static Schema MixedSchema(bool isSingleton = false) { - var schema = new Schema("user") + var schema = new Schema("user", isSingleton: isSingleton) .Publish() .AddArray(101, "root-array", Partitioning.Language, f => f .AddAssets(201, "nested-assets") diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 101b9ce3a..2d257f179 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -353,19 +353,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Name = "field1", Properties = ValidProperties(), - Partitioning = "invariant" + Partitioning = Partitioning.Invariant.Key }, new UpsertSchemaField { Name = "field2", Properties = ValidProperties(), - Partitioning = "invariant" + Partitioning = Partitioning.Invariant.Key }, new UpsertSchemaField { Name = "field3", Properties = new ArrayFieldProperties(), - Partitioning = "invariant", + Partitioning = Partitioning.Invariant.Key, Nested = new List { new UpsertSchemaNestedField diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs index 5e88d8bae..416a8022e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs @@ -142,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas { Scripts = new Dictionary { - ["Query"] = "" + [Scripts.Query] = "" } }; @@ -637,6 +637,46 @@ namespace Squidex.Domain.Apps.Entities.Schemas ); } + [Fact] + public async Task Synchronize_should_create_events_and_update_state() + { + var command = new SynchronizeSchema + { + Scripts = new Dictionary + { + [Scripts.Query] = "" + }, + PreviewUrls = new Dictionary + { + ["Web"] = "web-url" + }, + Fields = new List + { + new UpsertSchemaField { Name = fieldId.Name, Properties = ValidProperties() } + }, + Category = "My-Category" + }; + + await ExecuteCreateAsync(); + + var result = await sut.ExecuteAsync(CreateCommand(command)); + + result.ShouldBeEquivalent(new EntitySavedResult(4)); + + Assert.NotNull(GetField(1)); + Assert.Equal(command.Category, sut.Snapshot.SchemaDef.Category); + Assert.Equal(command.Scripts, sut.Snapshot.SchemaDef.Scripts); + Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls); + + LastEvents + .ShouldHaveSameEvents( + CreateEvent(new SchemaCategoryChanged { Name = command.Category }), + CreateEvent(new SchemaScriptsConfigured { Scripts = command.Scripts }), + CreateEvent(new SchemaPreviewUrlsConfigured { PreviewUrls = command.PreviewUrls }), + CreateEvent(new FieldAdded { FieldId = fieldId, Name = fieldId.Name, Properties = command.Fields[0].Properties, Partitioning = Partitioning.Invariant.Key }) + ); + } + private Task ExecuteCreateAsync() { return sut.ExecuteAsync(CreateCommand(new CreateSchema { Name = SchemaName })); diff --git a/tools/Migrate_01/Rebuilder.cs b/tools/Migrate_01/Rebuilder.cs index f29f924c2..ee77eb829 100644 --- a/tools/Migrate_01/Rebuilder.cs +++ b/tools/Migrate_01/Rebuilder.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.State; using Squidex.Domain.Apps.Entities.Assets; From 25a89b729fb50e4247d3e811289a423b9abb54d3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 27 Jan 2019 15:03:44 +0100 Subject: [PATCH 4/7] Updates --- .../Squidex.Extensions.csproj | 2 +- .../Schemas/Scripts.cs | 5 +- ...Squidex.Domain.Apps.Core.Operations.csproj | 2 +- ...quidex.Domain.Apps.Entities.MongoDb.csproj | 2 +- .../Contents/ContentGrain.cs | 11 +++-- .../Contents/ContentOperationContext.cs | 14 ++++-- .../Contents/ContentQueryService.cs | 3 +- .../Squidex.Domain.Apps.Entities.csproj | 2 +- .../Squidex.Domain.Users.MongoDb.csproj | 2 +- .../Squidex.Infrastructure.MongoDb.csproj | 4 +- .../Squidex.Infrastructure.csproj | 4 +- src/Squidex/Squidex.csproj | 12 ++--- .../SchemaSynchronizerTests.cs | 2 +- .../Squidex.Domain.Apps.Core.Tests.csproj | 4 +- .../Contents/ContentQueryServiceTests.cs | 6 +-- .../Squidex.Domain.Apps.Entities.Tests.csproj | 6 +-- .../Squidex.Domain.Users.Tests.csproj | 4 +- .../Grains/EventConsumerGrainTests.cs | 46 +++++++++---------- .../EventSourcing/PollingSubscriptionTests.cs | 4 +- .../EventSourcing/RetrySubscriptionTests.cs | 4 +- .../Migrations/MigratorTests.cs | 4 +- .../Orleans/BootstrapTests.cs | 4 +- .../Squidex.Infrastructure.Tests.csproj | 4 +- .../UsageTracking/CachingUsageTrackerTests.cs | 4 +- tests/Squidex.Tests/Squidex.Tests.csproj | 4 +- tools/Migrate_00/Migrate_00.csproj | 2 +- 26 files changed, 85 insertions(+), 76 deletions(-) diff --git a/extensions/Squidex.Extensions/Squidex.Extensions.csproj b/extensions/Squidex.Extensions/Squidex.Extensions.csproj index 1756e0c3e..4f8e9fcb0 100644 --- a/extensions/Squidex.Extensions/Squidex.Extensions.csproj +++ b/extensions/Squidex.Extensions/Squidex.Extensions.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs index 30914130d..93eeeb474 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs @@ -14,9 +14,10 @@ namespace Squidex.Domain.Apps.Core.Schemas { public const string Change = "Change"; public const string Create = "Create"; - public const string Query = "Query"; - public const string Update = "Update"; public const string Delete = "Delete"; + public const string Update = "Update"; + + public const string Query = "Query"; public static string GetChange(this IReadOnlyDictionary scripts) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index 3fc9981a9..a2f8491f7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj index 8472247c5..370001c52 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index e2b4e6422..d09431545 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -8,6 +8,7 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; @@ -66,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanCreate(ctx.Schema, c); - await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Create"), "Create", c, c.Data); + await ctx.ExecuteScriptAndTransformAsync(Scripts.Create, "Create", c, c.Data); await ctx.EnrichAsync(c.Data); if (!c.DoNotValidate) @@ -76,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (c.Publish) { - await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), "Published", c, c.Data); + await ctx.ExecuteScriptAsync(Scripts.Change, "Published", c, c.Data); } Create(c); @@ -140,7 +141,7 @@ namespace Squidex.Domain.Apps.Entities.Contents reason = StatusChange.Restored; } - await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), reason, c, Snapshot.Data); + await ctx.ExecuteScriptAsync(Scripts.Change, reason, c, Snapshot.Data); ChangeStatus(c, reason); } @@ -166,7 +167,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanDelete(ctx.Schema, c); - await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Delete"), "Delete", c, Snapshot.Data); + await ctx.ExecuteScriptAsync(Scripts.Delete, "Delete", c, Snapshot.Data); Delete(c); }); @@ -208,7 +209,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await ctx.ValidateAsync(c.Data); } - newData = await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Update"), "Update", c, newData, Snapshot.Data); + newData = await ctx.ExecuteScriptAndTransformAsync(Scripts.Update, "Update", c, newData, Snapshot.Data); if (isProposal) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index ca5c36a38..239cbbf91 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Tasks; @@ -86,20 +87,20 @@ namespace Squidex.Domain.Apps.Entities.Contents return data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message); } - public Task ExecuteScriptAndTransformAsync(Func script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) + public Task ExecuteScriptAndTransformAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) { var ctx = CreateScriptContext(operation, command, data, oldData); - var result = scriptEngine.ExecuteAndTransform(ctx, script(schemaEntity)); + var result = scriptEngine.ExecuteAndTransform(ctx, GetScript(script)); return Task.FromResult(result); } - public Task ExecuteScriptAsync(Func script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) + public Task ExecuteScriptAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) { var ctx = CreateScriptContext(operation, command, data, oldData); - scriptEngine.Execute(ctx, script(schemaEntity)); + scriptEngine.Execute(ctx, GetScript(script)); return TaskHelper.Done; } @@ -123,5 +124,10 @@ namespace Squidex.Domain.Apps.Entities.Contents { return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); } + + private string GetScript(string script) + { + return schemaEntity.SchemaDef.Scripts.GetOrDefault(script); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index a81b6017e..af2e3ca91 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Options; using Microsoft.OData; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Repositories; @@ -158,7 +159,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var converters = GenerateConverters(context, checkType).ToArray(); - var scriptText = schema.SchemaDef.Scripts.GetOrDefault("Query"); + var scriptText = schema.SchemaDef.Scripts.GetQuery(); var isScripting = !string.IsNullOrWhiteSpace(scriptText); diff --git a/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 5dd06b39a..6adb6a190 100644 --- a/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -16,7 +16,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj index 0dbf9512a..351faafbf 100644 --- a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj +++ b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj index 8d568fa96..683baf450 100644 --- a/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj +++ b/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index d1a86c1c2..5a7a3c178 100644 --- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -12,12 +12,12 @@ - + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 49cb6bcd3..cebe95677 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -62,7 +62,7 @@ - + @@ -85,15 +85,15 @@ - - - - + + + + - + diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs index e52c74a7e..caf692cce 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization { var scripts = new Dictionary { - ["Create"] = "Script" + [Scripts.Create] = "" }; var sourceSchema = new Schema("source"); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj index c61057acd..7de203167 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index d4313212e..0f0f9d4a7 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -202,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Equal(content.Id, result.Id); A.CallTo(() => scriptEngine.Transform(A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -301,7 +301,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Equal(total, result.Total); A.CallTo(() => scriptEngine.Transform(A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Times(count)); + .MustHaveHappened(count, Times.Exactly); } [Fact] @@ -377,7 +377,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Equal(total, result.Total); A.CallTo(() => scriptEngine.Transform(A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Times(count)); + .MustHaveHappened(count, Times.Exactly); } private void SetupClaims(bool isFrontend = false, bool allowSchema = true) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index b7df20bed..67f0203af 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -17,11 +17,11 @@ - - + + - + diff --git a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj index ca2daac0e..7e52cf91e 100644 --- a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj +++ b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs index d1423f98c..6a95aac4b 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs @@ -116,7 +116,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = false, Position = initialPosition, Error = null }); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -128,7 +128,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = false, Position = initialPosition, Error = null }); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -142,10 +142,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = true, Position = initialPosition, Error = null }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -159,19 +159,19 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = false, Position = null, Error = null }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); A.CallTo(() => eventConsumer.ClearAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, state.Position)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, null)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -187,10 +187,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = false, Position = @event.EventPosition, Error = null }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventConsumer.On(envelope)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -209,7 +209,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = false, Position = @event.EventPosition, Error = null }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventConsumer.On(envelope)) .MustNotHaveHappened(); @@ -244,10 +244,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = true, Position = initialPosition, Error = ex.ToString() }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -292,10 +292,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains state.Should().BeEquivalentTo(new EventConsumerState { IsStopped = true, Position = initialPosition, Error = ex.ToString() }); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -319,10 +319,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains .MustHaveHappened(); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -346,10 +346,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains .MustNotHaveHappened(); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -377,13 +377,13 @@ namespace Squidex.Infrastructure.EventSourcing.Grains .MustHaveHappened(); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); } private Task OnErrorAsync(IEventSubscription subscriber, Exception ex) diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs index 8f2e5ac6f..b62dab45d 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs @@ -27,7 +27,7 @@ namespace Squidex.Infrastructure.EventSourcing await WaitAndStopAsync(sut); A.CallTo(() => eventStore.QueryAsync(A>.Ignored, "^my-stream", position, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -88,7 +88,7 @@ namespace Squidex.Infrastructure.EventSourcing await WaitAndStopAsync(sut); A.CallTo(() => eventStore.QueryAsync(A>.Ignored, "^my-stream", position, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); } private static async Task WaitAndStopAsync(IEventSubscription sut) diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs index 10ad267e9..6231bc1b4 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs @@ -49,10 +49,10 @@ namespace Squidex.Infrastructure.EventSourcing await sut.StopAsync(); A.CallTo(() => eventSubscription.StopAsync()) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, A.Ignored)) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); A.CallTo(() => eventSubscriber.OnErrorAsync(A.Ignored, A.Ignored)) .MustNotHaveHappened(); diff --git a/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs b/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs index f9810e1fd..df56ca42b 100644 --- a/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs @@ -126,8 +126,8 @@ namespace Squidex.Infrastructure.Migrations await Task.WhenAll(Enumerable.Repeat(0, 10).Select(x => Task.Run(sut.MigrateAsync))); - A.CallTo(() => migrator_0_1.UpdateAsync()).MustHaveHappened(Repeated.Exactly.Once); - A.CallTo(() => migrator_1_2.UpdateAsync()).MustHaveHappened(Repeated.Exactly.Once); + A.CallTo(() => migrator_0_1.UpdateAsync()).MustHaveHappened(1, Times.Exactly); + A.CallTo(() => migrator_1_2.UpdateAsync()).MustHaveHappened(1, Times.Exactly); } private IMigration BuildMigration(int fromVersion, int toVersion) diff --git a/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs b/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs index 4131a1044..b2fb54fd8 100644 --- a/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Orleans/BootstrapTests.cs @@ -58,7 +58,7 @@ namespace Squidex.Infrastructure.Orleans await sut.Execute(CancellationToken.None); A.CallTo(() => grain.ActivateAsync()) - .MustHaveHappened(Repeated.Exactly.Twice); + .MustHaveHappened(2, Times.Exactly); } [Fact] @@ -70,7 +70,7 @@ namespace Squidex.Infrastructure.Orleans await Assert.ThrowsAsync(() => sut.Execute(CancellationToken.None)); A.CallTo(() => grain.ActivateAsync()) - .MustHaveHappened(Repeated.Exactly.Times(10)); + .MustHaveHappened(10, Times.Exactly); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj index d9b6cafc0..acc8be42c 100644 --- a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj +++ b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs index a8fa75b46..20208e0e3 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs @@ -57,7 +57,7 @@ namespace Squidex.Infrastructure.UsageTracking Assert.Equal(100, result2); A.CallTo(() => inner.GetMonthlyCallsAsync(key, DateTime.Today)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } [Fact] @@ -76,7 +76,7 @@ namespace Squidex.Infrastructure.UsageTracking Assert.Equal(120, result2); A.CallTo(() => inner.GetPreviousCallsAsync(key, f, t)) - .MustHaveHappened(Repeated.Exactly.Once); + .MustHaveHappened(1, Times.Exactly); } } } diff --git a/tests/Squidex.Tests/Squidex.Tests.csproj b/tests/Squidex.Tests/Squidex.Tests.csproj index 4abb88d74..8f6b4e0df 100644 --- a/tests/Squidex.Tests/Squidex.Tests.csproj +++ b/tests/Squidex.Tests/Squidex.Tests.csproj @@ -12,11 +12,11 @@ - + - + diff --git a/tools/Migrate_00/Migrate_00.csproj b/tools/Migrate_00/Migrate_00.csproj index 950b9fce6..dd3d5eb4a 100644 --- a/tools/Migrate_00/Migrate_00.csproj +++ b/tools/Migrate_00/Migrate_00.csproj @@ -6,7 +6,7 @@ 7.3 - + From e9433db0b3e4db764a57ccb62ab3912600fe09ba Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 28 Jan 2019 18:19:03 +0100 Subject: [PATCH 5/7] A lot of improvements. --- .../Schemas/Json/JsonSchemaModel.cs | 6 +- .../Schemas/Schema.cs | 30 +++++----- .../Schemas/SchemaExtensions.cs | 55 +++++++++++++++++++ .../Schemas/SchemaScripts.cs | 22 ++++++++ .../Schemas/Scripts.cs | 47 ---------------- .../SchemaSynchronizer.cs | 29 ++++++---- .../Apps/Templates/Builders/SchemaBuilder.cs | 35 +++++++++--- .../Templates/CreateBlogCommandMiddleware.cs | 23 ++------ .../CreateIdentityCommandMiddleware.cs | 10 +--- .../Apps/Templates/DefaultScripts.cs | 13 ++--- .../Contents/ContentGrain.cs | 11 ++-- .../Contents/ContentOperationContext.cs | 10 ++-- .../Contents/ContentQueryService.cs | 3 +- .../GraphQL/Types/ContentDataGraphType.cs | 1 + .../Contents/SingletonCommandMiddleware.cs | 2 +- .../Schemas/Commands/ConfigureScripts.cs | 4 +- .../Schemas/Commands/CreateSchema.cs | 4 +- .../Schemas/Commands/UpsertCommand.cs | 8 +-- .../Schemas/SchemaExtensions.cs | 44 +-------------- .../Schemas/SchemaHistoryEventsCreator.cs | 12 ++++ .../Schemas/SchemaScriptsConfigured.cs | 4 +- .../Swagger/XmlResponseTypesProcessor.cs | 22 +++----- .../Generator/SchemaSwaggerGenerator.cs | 2 +- ...riptsDto.cs => ConfigurePreviewUrlsDto.cs} | 8 +-- .../Schemas/Models/CreateSchemaDto.cs | 2 +- .../Schemas/Models/PreviewUrlsDto.cs | 15 ----- .../Schemas/Models/SchemaDetailsDto.cs | 38 +++++-------- .../Schemas/Models/SchemaScriptsDto.cs | 48 ++++++++++++++++ .../Controllers/Schemas/Models/UpsertDto.cs | 2 +- .../Schemas/SchemaFieldsController.cs | 2 - .../Controllers/Schemas/SchemasController.cs | 16 +++--- src/Squidex/Config/Domain/EntitiesServices.cs | 3 + .../schema/schema-scripts-form.component.html | 2 +- .../schema/schema-scripts-form.component.ts | 4 +- .../pages/schemas/schema-form.component.html | 8 +-- .../pages/schemas/schema-form.component.ts | 2 +- .../shared/services/schemas.service.spec.ts | 30 +++++----- .../app/shared/services/schemas.service.ts | 41 +++----------- .../app/shared/state/contents.forms.spec.ts | 2 +- src/Squidex/app/shared/state/schemas.forms.ts | 12 ++-- .../app/shared/state/schemas.state.spec.ts | 32 ++++------- src/Squidex/app/shared/state/schemas.state.ts | 13 ++--- .../Model/Schemas/SchemaTests.cs | 42 +++++++++++++- .../SchemaSynchronizerTests.cs | 27 ++++++++- .../Contents/ContentGrainTests.cs | 11 ++-- .../Contents/ContentQueryServiceTests.cs | 4 +- .../Contents/GraphQL/GraphQLTestBase.cs | 2 +- .../SingletonCommandMiddlewareTests.cs | 6 +- .../Schemas/SchemaGrainTests.cs | 12 ++-- tools/Migrate_01/MigrationPath.cs | 8 ++- tools/Migrate_01/Migrations/ClearSchemas.cs | 30 ++++++++++ .../Migrate_01/OldEvents/ScriptsConfigured.cs | 12 ++-- 52 files changed, 457 insertions(+), 374 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs delete mode 100644 src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs rename src/Squidex/Areas/Api/Controllers/Schemas/Models/{ConfigureScriptsDto.cs => ConfigurePreviewUrlsDto.cs} (67%) delete mode 100644 src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs create mode 100644 tools/Migrate_01/Migrations/ClearSchemas.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs index bfbf2c709..f7812ad7b 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs @@ -32,10 +32,10 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json public SchemaProperties Properties { get; set; } [JsonProperty] - public JsonFieldModel[] Fields { get; set; } + public SchemaScripts Scripts { get; set; } [JsonProperty] - public IReadOnlyDictionary Scripts { get; set; } + public JsonFieldModel[] Fields { get; set; } [JsonProperty] public IReadOnlyDictionary PreviewUrls { get; set; } @@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json schema = schema.ChangeCategory(Category); } - if (Scripts?.Count > 0) + if (Scripts != null) { schema = schema.ConfigureScripts(Scripts); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs index 05ee7b068..edddf4103 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs @@ -14,14 +14,13 @@ namespace Squidex.Domain.Apps.Core.Schemas { public sealed class Schema : Cloneable { - private static readonly Dictionary EmptyScripts = new Dictionary(); private static readonly Dictionary EmptyPreviewUrls = new Dictionary(); private readonly string name; private readonly bool isSingleton; private string category; private FieldCollection fields = FieldCollection.Empty; - private IReadOnlyDictionary scripts = EmptyScripts; private IReadOnlyDictionary previewUrls = EmptyPreviewUrls; + private SchemaScripts scripts = new SchemaScripts(); private SchemaProperties properties; private bool isPublished; @@ -60,11 +59,6 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields.ByName; } } - public IReadOnlyDictionary Scripts - { - get { return scripts; } - } - public IReadOnlyDictionary PreviewUrls { get { return previewUrls; } @@ -75,6 +69,11 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields; } } + public SchemaScripts Scripts + { + get { return scripts; } + } + public SchemaProperties Properties { get { return properties; } @@ -115,38 +114,39 @@ namespace Squidex.Domain.Apps.Core.Schemas } [Pure] - public Schema Publish() + public Schema ConfigureScripts(SchemaScripts newScripts) { return Clone(clone => { - clone.isPublished = true; + clone.scripts = newScripts ?? new SchemaScripts(); + clone.scripts.Freeze(); }); } [Pure] - public Schema Unpublish() + public Schema Publish() { return Clone(clone => { - clone.isPublished = false; + clone.isPublished = true; }); } [Pure] - public Schema ChangeCategory(string category) + public Schema Unpublish() { return Clone(clone => { - clone.category = category; + clone.isPublished = false; }); } [Pure] - public Schema ConfigureScripts(IReadOnlyDictionary scripts) + public Schema ChangeCategory(string category) { return Clone(clone => { - clone.scripts = scripts ?? EmptyScripts; + clone.category = category; }); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs new file mode 100644 index 000000000..b1d672216 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs @@ -0,0 +1,55 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; +using System; + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public static class SchemaExtensions + { + public static long MaxId(this Schema schema) + { + var id = 0L; + + foreach (var field in schema.Fields) + { + if (field is IArrayField arrayField) + { + foreach (var nestedField in arrayField.Fields) + { + id = Math.Max(id, nestedField.Id); + } + } + + id = Math.Max(id, field.Id); + } + + return id; + } + + public static string TypeName(this IField field) + { + return field.Name.ToPascalCase(); + } + + public static string DisplayName(this IField field) + { + return field.RawProperties.Label.WithFallback(field.TypeName()); + } + + public static string TypeName(this Schema schema) + { + return schema.Name.ToPascalCase(); + } + + public static string DisplayName(this Schema schema) + { + return schema.Properties.Label.WithFallback(schema.TypeName()); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs new file mode 100644 index 000000000..ae3990180 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public sealed class SchemaScripts : Freezable + { + public string Change { get; set; } + + public string Create { get; set; } + + public string Update { get; set; } + + public string Delete { get; set; } + + public string Query { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs deleted file mode 100644 index 93eeeb474..000000000 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure; -using System.Collections.Generic; - -namespace Squidex.Domain.Apps.Core.Schemas -{ - public static class Scripts - { - public const string Change = "Change"; - public const string Create = "Create"; - public const string Delete = "Delete"; - public const string Update = "Update"; - - public const string Query = "Query"; - - public static string GetChange(this IReadOnlyDictionary scripts) - { - return scripts?.GetOrDefault(Change); - } - - public static string GetCreate(this IReadOnlyDictionary scripts) - { - return scripts?.GetOrDefault(Create); - } - - public static string GetQuery(this IReadOnlyDictionary scripts) - { - return scripts?.GetOrDefault(Query); - } - - public static string GetUpdate(this IReadOnlyDictionary scripts) - { - return scripts?.GetOrDefault(Update); - } - - public static string GetDelete(this IReadOnlyDictionary scripts) - { - return scripts?.GetOrDefault(Delete); - } - } -} diff --git a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs index cc693745c..cebdfbf99 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SchemaSynchronizer.cs @@ -48,9 +48,9 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization yield return E(new SchemaCategoryChanged { Name = target.Category }); } - if (!source.Scripts.EqualsDictionary(target.Scripts)) + if (!source.Scripts.EqualsJson(target.Scripts, serializer)) { - yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts.ToDictionary(x => x.Key, x => x.Value) }); + yield return E(new SchemaScriptsConfigured { Scripts = target.Scripts }); } if (!source.PreviewUrls.EqualsDictionary(target.PreviewUrls)) @@ -115,6 +115,8 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization if (source.ByName.TryGetValue(targetField.Name, out var sourceField)) { + canCreateField = false; + id = sourceField.NamedId(); if (CanUpdate(sourceField, targetField)) @@ -126,14 +128,16 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization } else if (!sourceField.IsLocked && !options.NoFieldRecreation) { + canCreateField = true; + + sourceIds.Remove(id); + sourceNames.Remove(id.Name); + yield return E(new FieldDeleted { FieldId = id }); } - else - { - canCreateField = false; - } } - else if (canCreateField) + + if (canCreateField) { var partitioning = (string)null; @@ -192,11 +196,14 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization } } - var targetNames = target.Ordered.Select(x => x.Name); - - if (sourceNames.Intersect(targetNames).Count() == target.Ordered.Count && !sourceNames.SequenceEqual(targetNames)) + if (sourceNames.Count > 1) { - yield return new SchemaFieldsReordered { FieldIds = sourceIds.Select(x => x.Id).ToList(), ParentFieldId = parentId }; + var targetNames = target.Ordered.Select(x => x.Name); + + if (sourceNames.Intersect(targetNames).Count() == target.Ordered.Count && !sourceNames.SequenceEqual(targetNames)) + { + yield return new SchemaFieldsReordered { FieldIds = sourceIds.Select(x => x.Id).ToList(), ParentFieldId = parentId }; + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs index c66ce4d39..bc588ebb5 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs @@ -24,20 +24,39 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders public static SchemaBuilder Create(string name) { + var schemaName = name.ToKebabCase(); + return new SchemaBuilder(new CreateSchema { - Name = name.ToKebabCase(), - Publish = true, - Properties = new SchemaProperties - { - Label = name - } - }); + Name = schemaName + }).Published().WithLabel(name); + } + + public SchemaBuilder WithLabel(string label) + { + command.Properties = command.Properties ?? new SchemaProperties(); + command.Properties.Label = label; + + return this; + } + + public SchemaBuilder WithScripts(SchemaScripts scripts) + { + command.Scripts = scripts; + + return this; + } + + public SchemaBuilder Published() + { + command.IsPublished = true; + + return this; } public SchemaBuilder Singleton() { - command.Singleton = true; + command.IsSingleton = true; return this; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs index c684477ff..35576796a 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Templates.Builders; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -106,19 +105,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates .Disabled() .Label("Slug (Autogenerated)") .Hints("Autogenerated slug that can be used to identity the post.")) + .WithScripts(DefaultScripts.GenerateSlug) .Build(); await publish(schema); - var schemaId = NamedId.Of(schema.SchemaId, schema.Name); - - await publish(new ConfigureScripts - { - SchemaId = schemaId.Id, - Scripts = DefaultScripts.GenerateSlug - }); - - return schemaId; + return NamedId.Of(schema.SchemaId, schema.Name); } private static async Task> CreatePagesSchemaAsync(Func publish) @@ -139,19 +131,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates .Disabled() .Label("Slug (Autogenerated)") .Hints("Autogenerated slug that can be used to identity the page.")) + .WithScripts(DefaultScripts.GenerateSlug) .Build(); await publish(schema); - var schemaId = NamedId.Of(schema.SchemaId, schema.Name); - - await publish(new ConfigureScripts - { - SchemaId = schemaId.Id, - Scripts = DefaultScripts.GenerateSlug - }); - - return schemaId; + return NamedId.Of(schema.SchemaId, schema.Name); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs index 14927ca2b..7ae11260e 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs @@ -9,7 +9,6 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Templates.Builders; -using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -229,17 +228,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates .AddString("Security Stamp", f => f .Disabled() .Hints("Internal security stamp")) + .WithScripts(DefaultScripts.GenerateUsername) .Build(); await publish(schema); - - var schemaId = NamedId.Of(schema.SchemaId, schema.Name); - - await publish(new ConfigureScripts - { - SchemaId = schemaId.Id, - Scripts = DefaultScripts.GenerateUsername - }); } private static Task CreateApiResourcesSchemaAsync(Func publish) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs index ba6df11ab..0144dc10f 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Apps.Templates @@ -34,16 +33,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates replace(data);"; - public static readonly Dictionary GenerateSlug = new Dictionary + public static readonly SchemaScripts GenerateSlug = new SchemaScripts { - [Scripts.Create] = ScriptToGenerateSlug, - [Scripts.Update] = ScriptToGenerateSlug + Create = ScriptToGenerateSlug, + Update = ScriptToGenerateSlug }; - public static readonly Dictionary GenerateUsername = new Dictionary + public static readonly SchemaScripts GenerateUsername = new SchemaScripts { - [Scripts.Create] = ScriptToGenerateUsername, - [Scripts.Update] = ScriptToGenerateUsername + Create = ScriptToGenerateUsername, + Update = ScriptToGenerateUsername }; } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index d09431545..f934c0deb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; @@ -67,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanCreate(ctx.Schema, c); - await ctx.ExecuteScriptAndTransformAsync(Scripts.Create, "Create", c, c.Data); + await ctx.ExecuteScriptAndTransformAsync(s => s.Create, "Create", c, c.Data); await ctx.EnrichAsync(c.Data); if (!c.DoNotValidate) @@ -77,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (c.Publish) { - await ctx.ExecuteScriptAsync(Scripts.Change, "Published", c, c.Data); + await ctx.ExecuteScriptAsync(s => s.Change, "Published", c, c.Data); } Create(c); @@ -141,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents reason = StatusChange.Restored; } - await ctx.ExecuteScriptAsync(Scripts.Change, reason, c, Snapshot.Data); + await ctx.ExecuteScriptAsync(s => s.Change, reason, c, Snapshot.Data); ChangeStatus(c, reason); } @@ -167,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents GuardContent.CanDelete(ctx.Schema, c); - await ctx.ExecuteScriptAsync(Scripts.Delete, "Delete", c, Snapshot.Data); + await ctx.ExecuteScriptAsync(s => s.Delete, "Delete", c, Snapshot.Data); Delete(c); }); @@ -209,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await ctx.ValidateAsync(c.Data); } - newData = await ctx.ExecuteScriptAndTransformAsync(Scripts.Update, "Update", c, newData, Snapshot.Data); + newData = await ctx.ExecuteScriptAndTransformAsync(s => s.Update, "Update", c, newData, Snapshot.Data); if (isProposal) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 239cbbf91..9d851ece7 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.EnrichContent; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Entities.Apps; @@ -17,7 +18,6 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Tasks; @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents return data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message); } - public Task ExecuteScriptAndTransformAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) + public Task ExecuteScriptAndTransformAsync(Func script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) { var ctx = CreateScriptContext(operation, command, data, oldData); @@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents return Task.FromResult(result); } - public Task ExecuteScriptAsync(string script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) + public Task ExecuteScriptAsync(Func script, object operation, ContentCommand command, NamedContentData data, NamedContentData oldData = null) { var ctx = CreateScriptContext(operation, command, data, oldData); @@ -125,9 +125,9 @@ namespace Squidex.Domain.Apps.Entities.Contents return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); } - private string GetScript(string script) + private string GetScript(Func script) { - return schemaEntity.SchemaDef.Scripts.GetOrDefault(script); + return script(schemaEntity.SchemaDef.Scripts); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index af2e3ca91..14570304c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -14,7 +14,6 @@ using Microsoft.Extensions.Options; using Microsoft.OData; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Repositories; @@ -159,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var converters = GenerateConverters(context, checkType).ToArray(); - var scriptText = schema.SchemaDef.Scripts.GetQuery(); + var scriptText = schema.SchemaDef.Scripts.Query; var isScripting = !string.IsNullOrWhiteSpace(scriptText); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index 20358dd65..6c930f903 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -10,6 +10,7 @@ using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs index 4b11b746e..a9695f500 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (context.IsCompleted && context.Command is CreateSchema createSchema && - createSchema.Singleton) + createSchema.IsSingleton) { var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name); diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs index 7012a91c1..5e9528a2c 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs @@ -5,12 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class ConfigureScripts : SchemaCommand { - public Dictionary Scripts { get; set; } + public SchemaScripts Scripts { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs index 22c4b746b..cc882abe4 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands public string Name { get; set; } - public bool Singleton { get; set; } + public bool IsSingleton { get; set; } public CreateSchema() { @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands public Schema ToSchema() { - return ToSchema(Name, Singleton); + return ToSchema(Name, IsSingleton); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs index 0ab6fa7cf..9b9e6bbe9 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs @@ -14,15 +14,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public abstract class UpsertCommand : SchemaCommand { - public bool Publish { get; set; } + public bool IsPublished { get; set; } public string Category { get; set; } public SchemaFields Fields { get; set; } - public SchemaProperties Properties { get; set; } + public SchemaScripts Scripts { get; set; } - public Dictionary Scripts { get; set; } + public SchemaProperties Properties { get; set; } public Dictionary PreviewUrls { get; set; } @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Commands { var schema = new Schema(name, Properties, isSingleton); - if (Publish) + if (IsPublished) { schema = schema.Publish(); } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs index d2f82939e..9d9cb5e29 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs @@ -19,59 +19,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name); } - public static long MaxId(this Schema schema) - { - var id = 0L; - - foreach (var field in schema.Fields) - { - if (field is IArrayField arrayField) - { - foreach (var nestedField in arrayField.Fields) - { - id = Math.Max(id, nestedField.Id); - } - } - - id = Math.Max(id, field.Id); - } - - return id; - } - public static string EscapePartition(this string value) { return value.Replace('-', '_'); } - public static string TypeName(this IField field) - { - return field.Name.ToPascalCase(); - } - public static string TypeName(this ISchemaEntity schema) { - return schema.SchemaDef.Name.ToPascalCase(); - } - - public static string DisplayName(this IField field) - { - return field.RawProperties.Label.WithFallback(field.TypeName()); + return schema.SchemaDef.TypeName(); } public static string DisplayName(this ISchemaEntity schema) { - return schema.SchemaDef.Properties.Label.WithFallback(schema.TypeName()); - } - - public static string TypeName(this Schema schema) - { - return schema.Name.ToPascalCase(); - } - - public static string DisplayName(this Schema schema) - { - return schema.Properties.Label.WithFallback(schema.TypeName()); + return schema.SchemaDef.DisplayName(); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs index 45fbd966f..2d6b9e484 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs @@ -19,6 +19,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas public SchemaHistoryEventsCreator(TypeNameRegistry typeNameRegistry) : base(typeNameRegistry) { + AddEventMessage("SchemaCreated", + "created schema {[Name]}."); + + AddEventMessage("ScriptsConfigured", + "configured script of schema {[Name]}."); + + AddEventMessage( + "reordered fields of schema {[Name]}."); + AddEventMessage( "created schema {[Name]}."); @@ -37,6 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas AddEventMessage( "reordered fields of schema {[Name]}."); + AddEventMessage( + "configured script of schema {[Name]}."); + AddEventMessage( "added field {[Field]} to schema {[Name]}."); diff --git a/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs b/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs index 1deeabd55..f0bb9bea9 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/SchemaScriptsConfigured.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events.Schemas @@ -13,6 +13,6 @@ namespace Squidex.Domain.Apps.Events.Schemas [EventType(nameof(SchemaScriptsConfigured))] public sealed class SchemaScriptsConfigured : SchemaEvent { - public Dictionary Scripts { get; set; } + public SchemaScripts Scripts { get; set; } } } diff --git a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs index e2297a41d..6d969c204 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using NJsonSchema.Infrastructure; @@ -23,8 +24,6 @@ namespace Squidex.Areas.Api.Config.Swagger public async Task ProcessAsync(OperationProcessorContext context) { - var hasOkResponse = false; - var operation = context.OperationDescription.Operation; var returnsDescription = await context.MethodInfo.GetXmlDocumentationTagAsync("returns") ?? string.Empty; @@ -41,19 +40,11 @@ namespace Squidex.Areas.Api.Config.Swagger } response.Description = match.Groups["Description"].Value; - - if (statusCode == "200" || statusCode == "204") - { - hasOkResponse = true; - } } await AddInternalErrorResponseAsync(context, operation); - if (!hasOkResponse) - { - RemoveOkResponse(operation); - } + CleanupResponses(operation); return true; } @@ -66,11 +57,14 @@ namespace Squidex.Areas.Api.Config.Swagger } } - private static void RemoveOkResponse(SwaggerOperation operation) + private static void CleanupResponses(SwaggerOperation operation) { - if (operation.Responses.TryGetValue("200", out var response) && response.Description?.Contains("=>") == true) + foreach (var (code, response) in operation.Responses.ToList()) { - operation.Responses.Remove("200"); + if (string.IsNullOrWhiteSpace(response.Description) || response.Description?.Contains("=>") == true) + { + operation.Responses.Remove(code); + } } } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs index 270cb5e7f..703e77ce3 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs @@ -230,7 +230,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator }); } - private SwaggerPathItem AddOperation(SwaggerOperationMethod method, string entityName, string path, Action updater) + private SwaggerPathItem AddOperation(string method, string entityName, string path, Action updater) { var operations = document.Paths.GetOrAddNew(path); var operation = new SwaggerOperation(); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigurePreviewUrlsDto.cs similarity index 67% rename from src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs rename to src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigurePreviewUrlsDto.cs index 8247b7179..22069636e 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigureScriptsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/ConfigurePreviewUrlsDto.cs @@ -1,7 +1,7 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== @@ -10,11 +10,11 @@ using Squidex.Domain.Apps.Entities.Schemas.Commands; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class ConfigureScriptsDto : Dictionary + public sealed class ConfigurePreviewUrlsDto : Dictionary { - public ConfigureScripts ToCommand() + public ConfigurePreviewUrls ToCommand() { - return new ConfigureScripts { Scripts = this }; + return new ConfigurePreviewUrls { PreviewUrls = this }; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs index a2b8e3299..8f969a4bb 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs @@ -22,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// /// Set to true to allow a single content item only. /// - public bool Singleton { get; set; } + public bool IsSingleton { get; set; } public CreateSchema ToCommand() { diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs deleted file mode 100644 index 39c68aef1..000000000 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/PreviewUrlsDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; - -namespace Squidex.Areas.Api.Controllers.Schemas.Models -{ - public sealed class PreviewUrlsDto : Dictionary - { - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index 97c9647c8..a6ed2b46b 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -19,6 +19,8 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models { public sealed class SchemaDetailsDto { + private static readonly Dictionary EmptyPreviewUrls = new Dictionary(); + /// /// The id of the schema. /// @@ -47,34 +49,14 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models public bool IsPublished { get; set; } /// - /// The script that is executed for each query when querying contents. - /// - public string ScriptQuery { get; set; } - - /// - /// The script that is executed when creating a content. - /// - public string ScriptCreate { get; set; } - - /// - /// The script that is executed when updating a content. - /// - public string ScriptUpdate { get; set; } - - /// - /// The script that is executed when deleting a content. - /// - public string ScriptDelete { get; set; } - - /// - /// The script that is executed when changing a content status. + /// The scripts. /// - public string ScriptChange { get; set; } + public SchemaScriptsDto Scripts { get; set; } = new SchemaScriptsDto(); /// /// The preview Urls. /// - public Dictionary PreviewUrls { get; set; } + public Dictionary PreviewUrls { get; set; } = EmptyPreviewUrls; /// /// The list of fields. @@ -86,7 +68,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// The schema properties. /// [Required] - public SchemaPropertiesDto Properties { get; set; } + public SchemaPropertiesDto Properties { get; set; } = new SchemaPropertiesDto(); /// /// The user that has created the schema. @@ -117,12 +99,18 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models public static SchemaDetailsDto FromSchema(ISchemaEntity schema) { - var response = new SchemaDetailsDto { Properties = new SchemaPropertiesDto() }; + var response = new SchemaDetailsDto(); SimpleMapper.Map(schema, response); SimpleMapper.Map(schema.SchemaDef, response); + SimpleMapper.Map(schema.SchemaDef.Scripts, response.Scripts); SimpleMapper.Map(schema.SchemaDef.Properties, response.Properties); + if (schema.SchemaDef.PreviewUrls.Count > 0) + { + response.PreviewUrls = new Dictionary(schema.SchemaDef.PreviewUrls); + } + response.Fields = new List(); foreach (var field in schema.SchemaDef.Fields) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs new file mode 100644 index 000000000..3e2fd8a2d --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Schemas.Models +{ + public sealed class SchemaScriptsDto + { + /// + /// The script that is executed for each query when querying contents. + /// + public string Query { get; set; } + + /// + /// The script that is executed when creating a content. + /// + public string Create { get; set; } + + /// + /// The script that is executed when updating a content. + /// + public string Update { get; set; } + + /// + /// The script that is executed when deleting a content. + /// + public string Delete { get; set; } + + /// + /// The script that is executed when change a content status. + /// + public string Change { get; set; } + + public ConfigureScripts ToCommand() + { + var scripts = SimpleMapper.Map(this, new SchemaScripts()); + + return new ConfigureScripts { Scripts = scripts }; + } + } +} \ No newline at end of file diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs index 0e199c6cf..8d114940f 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/UpsertDto.cs @@ -42,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// /// Set it to true to autopublish the schema. /// - public bool Publish { get; set; } + public bool IsPublished { get; set; } public static TCommand ToCommand(TDto dto, TCommand command) where TCommand : UpsertCommand where TDto : UpsertDto { diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 00850058e..048607c97 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -146,7 +146,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")] - [ProducesResponseType(typeof(ErrorDto), 409)] [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] @@ -172,7 +171,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")] - [ProducesResponseType(typeof(ErrorDto), 409)] [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index b867cdd2e..1da3dd5f3 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -132,7 +132,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The schema object that needs to updated. /// - /// 204 => Schema has been updated. + /// 204 => Schema updated. /// 400 => Schema properties are not valid. /// 404 => Schema or app not found. /// @@ -154,7 +154,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The schema object that needs to updated. /// - /// 204 => Schema has been updated. + /// 204 => Schema updated. /// 400 => Schema properties are not valid. /// 404 => Schema or app not found. /// @@ -176,7 +176,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The schema object that needs to updated. /// - /// 204 => Schema has been updated. + /// 204 => Schema updated. /// 404 => Schema or app not found. /// [HttpPut] @@ -197,16 +197,16 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The preview urls for the schema. /// - /// 204 => Schema has been updated. + /// 204 => Schema updated. /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/preview-urls")] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] - public async Task PutPreviewUrls(string app, string name, [FromBody] PreviewUrlsDto request) + public async Task PutPreviewUrls(string app, string name, [FromBody] ConfigurePreviewUrlsDto request) { - await CommandBus.PublishAsync(new ConfigurePreviewUrls { PreviewUrls = request ?? new PreviewUrlsDto() }); + await CommandBus.PublishAsync(request.ToCommand()); return NoContent(); } @@ -218,7 +218,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The schema scripts object that needs to updated. /// - /// 204 => Schema has been updated. + /// 204 => Schema updated. /// 400 => Schema properties are not valid. /// 404 => Schema or app not found. /// @@ -226,7 +226,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas [Route("apps/{app}/schemas/{name}/scripts/")] [ApiPermission(Permissions.AppSchemasScripts)] [ApiCosts(1)] - public async Task PutSchemaScripts(string app, string name, [FromBody] ConfigureScriptsDto request) + public async Task PutSchemaScripts(string app, string name, [FromBody] SchemaScriptsDto request) { await CommandBus.PublishAsync(request.ToCommand()); diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 4401d09b8..98641c63f 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -251,6 +251,9 @@ namespace Squidex.Config.Domain services.AddTransientAs() .As(); + services.AddTransientAs() + .As(); + services.AddTransientAs() .As(); diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html index 090907620..74f2705aa 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.html @@ -7,7 +7,7 @@ diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts index a48809b1e..ba545655a 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-scripts-form.component.ts @@ -26,7 +26,7 @@ export class SchemaScriptsFormComponent implements OnInit { @Input() public schema: SchemaDetailsDto; - public selectedField = 'scriptQuery'; + public selectedField = 'query'; public editForm = new EditScriptsForm(this.formBuilder); @@ -37,7 +37,7 @@ export class SchemaScriptsFormComponent implements OnInit { } public ngOnInit() { - this.editForm.load(this.schema); + this.editForm.load(this.schema.scripts); } public complete() { diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html index ac0fd5016..509579d6e 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -34,11 +34,11 @@