From baae8eeb522097555efcabf8b4410d6f8ef2d482 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 12:03:19 +0100 Subject: [PATCH 01/10] Core models added. --- .../Apps/AppPattern.cs | 62 ++++++++++++++ .../Apps/AppPatterns.cs | 57 +++++++++++++ .../Apps/Json/AppPatternsConverter.cs | 37 +++++++++ .../Apps/Json/JsonAppPattern.cs | 42 ++++++++++ .../Apps/AppPatternAdded.cs | 25 ++++++ .../Apps/AppPatternDeleted.cs | 21 +++++ .../Apps/AppPatternUpdated.cs | 25 ++++++ .../Model/Apps/AppPatternJsonTests.cs | 44 ++++++++++ .../Model/Apps/AppPatternsTests.cs | 80 +++++++++++++++++++ .../TestData.cs | 1 + 10 files changed, 394 insertions(+) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs create mode 100644 src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs create mode 100644 src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs create mode 100644 src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs new file mode 100644 index 000000000..a893f5f8e --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs @@ -0,0 +1,62 @@ +// ========================================================================== +// AppPattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Diagnostics.Contracts; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Apps +{ + public class AppPattern + { + private readonly Guid id; + private readonly string name; + private readonly string pattern; + private readonly string defaultMessage; + + public Guid Id + { + get { return id; } + } + + public string Name + { + get { return name; } + } + + public string Pattern + { + get { return pattern; } + } + + public string DefaultMessage + { + get { return defaultMessage; } + } + + public AppPattern(Guid id, string name, string pattern, string defaultMessage) + { + Guard.NotNullOrEmpty(name, nameof(name)); + Guard.NotNullOrEmpty(pattern, nameof(pattern)); + + this.id = id; + this.name = name; + this.pattern = pattern; + this.defaultMessage = defaultMessage; + } + + [Pure] + public AppPattern Update(string name, string pattern, string defaultMessage) + { + Guard.NotNullOrEmpty(name, nameof(name)); + Guard.NotNullOrEmpty(pattern, nameof(pattern)); + + return new AppPattern(this.id, name, pattern, defaultMessage); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs new file mode 100644 index 000000000..465c2cf67 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// AppPatterns.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== +using System; +using System.Collections.Immutable; +using System.Diagnostics.Contracts; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.Apps +{ + public sealed class AppPatterns : DictionaryWrapper + { + public static readonly AppPatterns Empty = new AppPatterns(); + + private AppPatterns() + : base(ImmutableDictionary.Empty) + { + } + + public AppPatterns(ImmutableDictionary inner) + : base(inner) + { + } + + [Pure] + public AppPatterns Add(Guid id, string name, string pattern, string defaultMessage) + { + var newPattern = new AppPattern(id, name, pattern, defaultMessage); + + return new AppPatterns(Inner.Add(id, newPattern)); + } + + [Pure] + public AppPatterns Remove(Guid id) + { + return new AppPatterns(Inner.Remove(id)); + } + + [Pure] + public AppPatterns Update(Guid id, string name, string pattern, string defaultMessage) + { + Guard.NotNullOrEmpty(name, nameof(name)); + Guard.NotNullOrEmpty(pattern, nameof(pattern)); + + if (!TryGetValue(id, out var appPattern)) + { + return this; + } + + return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, defaultMessage))); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs new file mode 100644 index 000000000..2e00fef01 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// AppPatternsConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Newtonsoft.Json; +using Squidex.Infrastructure.Json; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public sealed class AppPatternsConverter : JsonClassConverter + { + protected override void WriteValue(JsonWriter writer, AppPatterns value, JsonSerializer serializer) + { + var json = new Dictionary(value.Count); + + foreach (var client in value) + { + json.Add(client.Key, new JsonAppPattern(client.Value)); + } + + serializer.Serialize(writer, json); + } + + protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) + { + var json = serializer.Deserialize>(reader); + + return new AppPatterns(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToPattern())); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs new file mode 100644 index 000000000..4b30d9e15 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs @@ -0,0 +1,42 @@ +// ========================================================================== +// JsonAppPattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== +using System; +using Newtonsoft.Json; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public class JsonAppPattern + { + [JsonProperty] + public Guid Id { get; set; } + + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public string Pattern { get; set; } + + [JsonProperty] + public string DefaultMessage { get; set; } + + public JsonAppPattern() + { + } + + public JsonAppPattern(AppPattern pattern) + { + SimpleMapper.Map(pattern, this); + } + + public AppPattern ToPattern() + { + return new AppPattern(Id, Name, Pattern, DefaultMessage); + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs new file mode 100644 index 000000000..bbd6fd0be --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// AppPatternAdded.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Apps +{ + [EventType(nameof(AppPatternAdded))] + public sealed class AppPatternAdded : AppEvent + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string Pattern { get; set; } + + public string DefaultMessage { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs new file mode 100644 index 000000000..0a595e3d4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// AppPatternDeleted.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Apps +{ + [EventType(nameof(AppPatternDeleted))] + public sealed class AppPatternDeleted : AppEvent + { + public Guid Id { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs new file mode 100644 index 000000000..1f5908b87 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// AppPatternUpdated.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Apps +{ + [EventType(nameof(AppPatternUpdated))] + public sealed class AppPatternUpdated : AppEvent + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string Pattern { get; set; } + + public string DefaultMessage { get; set; } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs new file mode 100644 index 000000000..3f9d61349 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// AppClientJsonTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Apps +{ + public class AppPatternJsonTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + + [Fact] + public void Should_serialize_and_deserialize() + { + var patterns = AppPatterns.Empty; + + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + var guid3 = Guid.NewGuid(); + + patterns = patterns.Add(guid1, "Name1", "Pattern1", "Default"); + patterns = patterns.Add(guid2, "Name2", "Pattern2", "Default"); + patterns = patterns.Add(guid3, "Name3", "Pattern3", "Default"); + + patterns = patterns.Update(guid2, "Name2 Update", "Pattern2 Update", "Default2"); + + patterns = patterns.Remove(guid1); + + var appPatterns = JToken.FromObject(patterns, serializer).ToObject(serializer); + + appPatterns.ShouldBeEquivalentTo(patterns); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs new file mode 100644 index 000000000..fae8ac4f3 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs @@ -0,0 +1,80 @@ +// ========================================================================== +// AppClientsTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using FluentAssertions; +using Squidex.Domain.Apps.Core.Apps; +using Xunit; + +#pragma warning disable SA1310 // Field names must not contain underscore + +namespace Squidex.Domain.Apps.Core.Model.Apps +{ + public class AppPatternsTests + { + private readonly AppPatterns defaultPatterns; + private readonly Guid firstId = Guid.NewGuid(); + private readonly Guid id = Guid.NewGuid(); + + public AppPatternsTests() + { + defaultPatterns = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message"); + + id = Guid.NewGuid(); + } + + [Fact] + public void Should_add_pattern() + { + var patterns = defaultPatterns.Add(id, "NewPattern", "New Pattern", "Message"); + + patterns[id].ShouldBeEquivalentTo(new AppPattern(id, "NewPattern", "New Pattern", "Message")); + } + + [Fact] + public void Should_throw_exception_if_add_pattern_with_same_id() + { + var patterns = defaultPatterns.Add(id, "NewPattern", "New Pattern", "Message"); + + Assert.Throws(() => patterns.Add(id, "NewPattern", "New Pattern", "Message")); + } + + [Fact] + public void Should_update_pattern() + { + var patterns = defaultPatterns.Update(firstId, "UpdatePattern", "Update Pattern", "Message"); + + patterns[firstId].ShouldBeEquivalentTo(new AppPattern(firstId, "UpdatePattern", "Update Pattern", "Message")); + } + + [Fact] + public void Should_return_same_patterns_if_pattern_not_found() + { + var patterns = defaultPatterns.Update(id, "NewPattern", "NewPattern", "Message"); + + Assert.Same(defaultPatterns, patterns); + } + + [Fact] + public void Should_remove_pattern() + { + var patterns = defaultPatterns.Remove(firstId); + + Assert.Empty(patterns); + } + + [Fact] + public void Should_do_nothing_if_remove_pattern_not_found() + { + var patterns = defaultPatterns.Remove(id); + + Assert.NotSame(defaultPatterns, patterns); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs index a3a97cb50..6c872c678 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs @@ -35,6 +35,7 @@ namespace Squidex.Domain.Apps.Core ContractResolver = new ConverterContractResolver( new AppClientsConverter(), new AppContributorsConverter(), + new AppPatternsConverter(), new InstantConverter(), new LanguageConverter(), new LanguagesConfigConverter(), From 08b610fb94d1da55ebe7eb6ce2338e0af6028258 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 12:57:11 +0100 Subject: [PATCH 02/10] Updated domain object. --- .../Apps/AppCommandMiddleware.cs | 30 ++++ .../Apps/AppDomainObject.cs | 27 +++ .../Apps/Commands/AddPattern.cs | 28 +++ .../Apps/Commands/CreateApp.cs | 4 +- .../Apps/Commands/DeletePattern.cs | 17 ++ .../Apps/Commands/UpdatePattern.cs | 23 +++ .../Apps/Guards/GuardAppPattern.cs | 97 ++++++++++ .../Apps/IAppEntity.cs | 2 + .../Apps/State/AppState.cs | 18 ++ .../Model/Apps/AppPatternsTests.cs | 3 - .../Apps/AppCommandMiddlewareTests.cs | 44 ++++- .../Apps/AppDomainObjectTests.cs | 78 ++++++++ .../Apps/Guards/GuardAppPatternsTests.cs | 169 ++++++++++++++++++ 13 files changed, 534 insertions(+), 6 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index 5a3a7f1d3..ccdf24331 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -138,6 +138,36 @@ namespace Squidex.Domain.Apps.Entities.Apps }); } + protected Task On(AddPattern command, CommandContext context) + { + return handler.UpdateAsync(context, a => + { + GuardAppPattern.CanAdd(a.State.Patterns, command); + + a.AddPattern(command); + }); + } + + protected Task On(DeletePattern command, CommandContext context) + { + return handler.UpdateAsync(context, a => + { + GuardAppPattern.CanDelete(a.State.Patterns, command); + + a.DeletePattern(command); + }); + } + + protected async Task On(UpdatePattern command, CommandContext context) + { + await handler.UpdateAsync(context, a => + { + GuardAppPattern.CanUpdate(a.State.Patterns, command); + + a.UpdatePattern(command); + }); + } + protected Task On(ChangePlan command, CommandContext context) { return handler.UpdateSyncedAsync(context, async a => diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index d11519917..6aae7ccfe 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -123,6 +123,33 @@ namespace Squidex.Domain.Apps.Entities.Apps return this; } + public AppDomainObject AddPattern(AddPattern command) + { + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppPatternAdded())); + + return this; + } + + public AppDomainObject DeletePattern(DeletePattern command) + { + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppPatternDeleted())); + + return this; + } + + public AppDomainObject UpdatePattern(UpdatePattern command) + { + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppPatternUpdated())); + + return this; + } + private void RaiseEvent(AppEvent @event) { if (@event.AppId == null) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs new file mode 100644 index 000000000..7432c4d81 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// AddPattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Entities.Apps.Commands +{ + public sealed class AddPattern : AppAggregateCommand + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string Pattern { get; set; } + + public string DefaultMessage { get; set; } + + public AddPattern() + { + Id = Guid.NewGuid(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs index eb173e4f5..57e1d18db 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs @@ -13,10 +13,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class CreateApp : SquidexCommand, IAggregateCommand { - public string Name { get; set; } - public Guid AppId { get; set; } + public string Name { get; set; } + Guid IAggregateCommand.AggregateId { get { return AppId; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs new file mode 100644 index 000000000..d152f40d0 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// DeletePattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Entities.Apps.Commands +{ + public sealed class DeletePattern : AppAggregateCommand + { + public Guid Id { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs new file mode 100644 index 000000000..20a436ba3 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// UpdatePattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Entities.Apps.Commands +{ + public sealed class UpdatePattern : AppAggregateCommand + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public string Pattern { get; set; } + + public string DefaultMessage { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs new file mode 100644 index 000000000..3aea26e1c --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs @@ -0,0 +1,97 @@ +// ========================================================================== +// GuardAppPattern.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== +using System; +using System.Linq; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Apps.Guards +{ + public static class GuardAppPattern + { + public static void CanAdd(AppPatterns patterns, AddPattern command) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot add pattern.", error => + { + if (string.IsNullOrWhiteSpace(command.Name)) + { + error(new ValidationError("Pattern name can not be empty.", nameof(command.Name))); + } + + if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) + { + error(new ValidationError("Pattern name is already assigned.", nameof(command.Name))); + } + + if (string.IsNullOrWhiteSpace(command.Pattern)) + { + error(new ValidationError("Pattern can not be empty.", nameof(command.Pattern))); + } + else if (!command.Pattern.IsValidRegex()) + { + error(new ValidationError("Pattern is not a valid regular expression.", nameof(command.Pattern))); + } + + if (patterns.Values.Any(x => x.Pattern == command.Pattern)) + { + error(new ValidationError("Pattern already exists.", nameof(command.Pattern))); + } + }); + } + + public static void CanDelete(AppPatterns patterns, DeletePattern command) + { + Guard.NotNull(command, nameof(command)); + + if (!patterns.ContainsKey(command.Id)) + { + throw new DomainObjectNotFoundException(command.Id.ToString(), typeof(AppPattern)); + } + } + + public static void CanUpdate(AppPatterns patterns, UpdatePattern command) + { + Guard.NotNull(command, nameof(command)); + + if (!patterns.ContainsKey(command.Id)) + { + throw new DomainObjectNotFoundException(command.Id.ToString(), typeof(AppPattern)); + } + + Validate.It(() => "Cannot update pattern.", error => + { + if (string.IsNullOrWhiteSpace(command.Name)) + { + error(new ValidationError("Pattern name can not be empty.", nameof(command.Name))); + } + + if (patterns.Any(x => x.Key != command.Id && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) + { + error(new ValidationError("Pattern name is already assigned.", nameof(command.Name))); + } + + if (string.IsNullOrWhiteSpace(command.Pattern)) + { + error(new ValidationError("Pattern can not be empty.", nameof(command.Pattern))); + } + else if (!command.Pattern.IsValidRegex()) + { + error(new ValidationError("Pattern is not a valid regular expression.", nameof(command.Pattern))); + } + + if (patterns.Any(x => x.Key != command.Id && x.Value.Pattern == command.Pattern)) + { + error(new ValidationError("Pattern already exists.", nameof(command.Pattern))); + } + }); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs index 154478f1e..85912cac2 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs @@ -18,6 +18,8 @@ namespace Squidex.Domain.Apps.Entities.Apps AppClients Clients { get; } + AppPatterns Patterns { get; } + AppContributors Contributors { get; } LanguagesConfig LanguagesConfig { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 35ed457a5..917140969 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -31,6 +31,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.State [JsonProperty] public AppClients Clients { get; set; } = AppClients.Empty; + [JsonProperty] + public AppPatterns Patterns { get; set; } = AppPatterns.Empty; + [JsonProperty] public AppContributors Contributors { get; set; } = AppContributors.Empty; @@ -77,6 +80,21 @@ namespace Squidex.Domain.Apps.Entities.Apps.State Clients = Clients.Revoke(@event.Id); } + protected void On(AppPatternAdded @event) + { + Patterns = Patterns.Add(@event.Id, @event.Name, @event.Pattern, @event.DefaultMessage); + } + + protected void On(AppPatternDeleted @event) + { + Patterns = Patterns.Remove(@event.Id); + } + + protected void On(AppPatternUpdated @event) + { + Patterns = Patterns.Update(@event.Id, @event.Name, @event.Pattern, @event.DefaultMessage); + } + protected void On(AppLanguageAdded @event) { LanguagesConfig = LanguagesConfig.Set(new LanguageConfig(@event.Language)); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs index fae8ac4f3..957df0e38 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs @@ -7,7 +7,6 @@ // ========================================================================== using System; -using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -25,8 +24,6 @@ namespace Squidex.Domain.Apps.Core.Model.Apps public AppPatternsTests() { defaultPatterns = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message"); - - id = Guid.NewGuid(); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index bb778526d..b07f64b1a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -26,10 +26,11 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly IAppPlansProvider appPlansProvider = A.Fake(); private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake(); private readonly IUserResolver userResolver = A.Fake(); - private readonly AppDomainObject app = new AppDomainObject(); private readonly Language language = Language.DE; private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientName = "client"; + private readonly Guid patternId = Guid.NewGuid(); + private readonly AppDomainObject app = new AppDomainObject(); private readonly AppCommandMiddleware sut; protected override Guid Id @@ -232,6 +233,47 @@ namespace Squidex.Domain.Apps.Entities.Apps }); } + [Fact] + public async Task AddPattern_should_update_domain_object() + { + CreateApp(); + + var context = CreateContextForCommand(new AddPattern { Name = "Any", Pattern = ".*" }); + + await TestUpdate(app, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task UpdatePattern_should_update_domain() + { + CreateApp() + .AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = "." })); + + var context = CreateContextForCommand(new UpdatePattern { Id = patternId, Name = "Number", Pattern = "[0-9]" }); + + await TestUpdate(app, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task DeletePattern_should_update_domain_object() + { + CreateApp() + .AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = "." })); + + var context = CreateContextForCommand(new DeletePattern { Id = patternId }); + + await TestUpdate(app, async _ => + { + await sut.HandleAsync(context); + }); + } + private AppDomainObject CreateApp() { app.Create(CreateCommand(new CreateApp { AppId = AppId, Name = AppName })); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index bea62e007..65bc15461 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -24,6 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly string clientId = "client"; private readonly string clientNewName = "My Client"; private readonly string planId = "premium"; + private readonly Guid patternId = Guid.NewGuid(); private readonly AppDomainObject sut = new AppDomainObject(); protected override Guid Id @@ -281,6 +282,83 @@ namespace Squidex.Domain.Apps.Entities.Apps ); } + [Fact] + public void AddPattern_should_throw_exception_if_app_not_created() + { + Assert.Throws(() => sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*" }))); + } + + [Fact] + public void AddPattern_should_create_events() + { + CreateApp(); + + sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" })); + + Assert.Single(sut.State.Patterns); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new AppPatternAdded { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" }) + ); + } + + [Fact] + public void DeletePattern_should_throw_exception_if_app_not_created() + { + Assert.Throws(() => + { + sut.DeletePattern(CreateCommand(new DeletePattern + { + Id = Guid.NewGuid() + })); + }); + } + + [Fact] + public void DeletePattern_should_create_events() + { + CreateApp(); + CreatePattern(); + + sut.DeletePattern(CreateCommand(new DeletePattern { Id = patternId })); + + Assert.Empty(sut.State.Patterns); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new AppPatternDeleted { Id = patternId }) + ); + } + + [Fact] + public void UpdatePattern_should_throw_exception_if_app_not_created() + { + Assert.Throws(() => sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*" }))); + } + + [Fact] + public void UpdatePattern_should_create_events() + { + CreateApp(); + CreatePattern(); + + sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" })); + + Assert.Single(sut.State.Patterns); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new AppPatternUpdated { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" }) + ); + } + + private void CreatePattern() + { + sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Name", Pattern = ".*" })); + sut.ClearUncommittedEvents(); + } + private void CreateApp() { sut.Create(CreateCommand(new CreateApp { Name = AppName })); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs new file mode 100644 index 000000000..e6f3202ef --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs @@ -0,0 +1,169 @@ +// ========================================================================== +// GuardAppPatternsTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Guards; +using Squidex.Infrastructure; +using Xunit; + +#pragma warning disable SA1310 // Field names must not contain underscore + +namespace Squidex.Domain.Apps.Write.Apps.Guards +{ + public class GuardAppPatternsTests + { + private readonly Guid patternId = Guid.NewGuid(); + private readonly AppPatterns patterns_0 = AppPatterns.Empty; + + [Fact] + public void CanAdd_should_throw_exception_if_name_empty() + { + var command = new AddPattern { Id = patternId, Name = string.Empty, Pattern = ".*" }; + + Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); + } + + [Fact] + public void CanAdd_should_throw_exception_if_id_empty_guid() + { + var command = new AddPattern { Name = string.Empty, Pattern = ".*" }; + + Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); + } + + [Fact] + public void CanAdd_should_throw_exception_if_pattern_empty() + { + var command = new AddPattern { Id = patternId, Name = "any", Pattern = string.Empty }; + + Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); + } + + [Fact] + public void CanAdd_should_throw_exception_if_pattern_not_valid() + { + var command = new AddPattern { Id = patternId, Name = "any", Pattern = "[0-9{1}" }; + + Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); + } + + [Fact] + public void CanAdd_should_throw_exception_if_name_exists() + { + var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message"); + + var command = new AddPattern { Id = patternId, Name = "any", Pattern = ".*" }; + + Assert.Throws(() => GuardAppPattern.CanAdd(patterns_1, command)); + } + + [Fact] + public void CanAdd_should_not_throw_exception_if_success() + { + var command = new AddPattern { Id = patternId, Name = "any", Pattern = ".*" }; + + GuardAppPattern.CanAdd(patterns_0, command); + } + + [Fact] + public void CanDelete_should_throw_exception_if_pattern_not_found() + { + var command = new DeletePattern { Id = patternId }; + + Assert.Throws(() => GuardAppPattern.CanDelete(patterns_0, command)); + } + + [Fact] + public void CanDelete_should_not_throw_exception_if_success() + { + var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); + + var command = new DeletePattern { Id = patternId }; + + GuardAppPattern.CanDelete(patterns_1, command); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_name_empty() + { + var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); + + var command = new UpdatePattern { Id = patternId, Name = string.Empty, Pattern = ".*" }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_pattern_empty() + { + var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); + + var command = new UpdatePattern { Id = patternId, Name = "any", Pattern = string.Empty }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_pattern_not_valid() + { + var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); + + var command = new UpdatePattern { Id = patternId, Name = "any", Pattern = "[0-9{1}" }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_name_exists() + { + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); + + var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); + var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); + + var command = new UpdatePattern { Id = id2, Name = "Pattern1", Pattern = "[0-4]" }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_2, command)); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_pattern_exists() + { + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); + + var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); + var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); + + var command = new UpdatePattern { Id = id2, Name = "Pattern2", Pattern = "[0-5]" }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_2, command)); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_pattern_does_not_exists() + { + var command = new UpdatePattern { Id = patternId, Name = "Pattern1", Pattern = ".*" }; + + Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_0, command)); + } + + [Fact] + public void CanUpdate_should_not_throw_exception_if_pattern_exist_with_valid_command() + { + var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); + + var command = new UpdatePattern { Id = patternId, Name = "Pattern1", Pattern = ".*" }; + + GuardAppPattern.CanUpdate(patterns_1, command); + } + } +} From 6d4e5899defbc632aa64ffa0061abc2153972fd5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 13:09:05 +0100 Subject: [PATCH 03/10] Added the API --- .../Apps/AppPattern.cs | 25 +--- .../Apps/AppPatterns.cs | 8 +- .../Apps/Json/JsonAppPattern.cs | 4 +- .../Apps/Commands/AddPattern.cs | 2 +- .../Apps/Commands/UpdatePattern.cs | 2 +- .../Apps/State/AppState.cs | 4 +- .../Apps/AppPatternAdded.cs | 2 +- .../Apps/AppPatternUpdated.cs | 2 +- .../Controllers/Apps/AppPatternsController.cs | 127 ++++++++++++++++++ .../Controllers/Apps/Models/AppPatternDto.cs | 38 ++++++ .../Apps/Models/UpdatePatternDto.cs | 32 +++++ .../Apps/AppDomainObjectTests.cs | 8 +- 12 files changed, 220 insertions(+), 34 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs index a893f5f8e..cfd0e6e8e 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs @@ -6,23 +6,16 @@ // All rights reserved. // ========================================================================== -using System; using System.Diagnostics.Contracts; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Apps { - public class AppPattern + public sealed class AppPattern { - private readonly Guid id; private readonly string name; private readonly string pattern; - private readonly string defaultMessage; - - public Guid Id - { - get { return id; } - } + private readonly string message; public string Name { @@ -34,29 +27,25 @@ namespace Squidex.Domain.Apps.Core.Apps get { return pattern; } } - public string DefaultMessage + public string Message { - get { return defaultMessage; } + get { return message; } } - public AppPattern(Guid id, string name, string pattern, string defaultMessage) + public AppPattern(string name, string pattern, string message) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(pattern, nameof(pattern)); - this.id = id; this.name = name; this.pattern = pattern; - this.defaultMessage = defaultMessage; + this.message = message; } [Pure] public AppPattern Update(string name, string pattern, string defaultMessage) { - Guard.NotNullOrEmpty(name, nameof(name)); - Guard.NotNullOrEmpty(pattern, nameof(pattern)); - - return new AppPattern(this.id, name, pattern, defaultMessage); + return new AppPattern(name, pattern, defaultMessage); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs index 465c2cf67..459ecc4a6 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs @@ -27,9 +27,9 @@ namespace Squidex.Domain.Apps.Core.Apps } [Pure] - public AppPatterns Add(Guid id, string name, string pattern, string defaultMessage) + public AppPatterns Add(Guid id, string name, string pattern, string message) { - var newPattern = new AppPattern(id, name, pattern, defaultMessage); + var newPattern = new AppPattern(name, pattern, message); return new AppPatterns(Inner.Add(id, newPattern)); } @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.Apps } [Pure] - public AppPatterns Update(Guid id, string name, string pattern, string defaultMessage) + public AppPatterns Update(Guid id, string name, string pattern, string message) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(pattern, nameof(pattern)); @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Apps return this; } - return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, defaultMessage))); + return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, message))); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs index 4b30d9e15..9c52a8198 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json public string Pattern { get; set; } [JsonProperty] - public string DefaultMessage { get; set; } + public string Message { get; set; } public JsonAppPattern() { @@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json public AppPattern ToPattern() { - return new AppPattern(Id, Name, Pattern, DefaultMessage); + return new AppPattern(Name, Pattern, Message); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs index 7432c4d81..12df12042 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands public string Pattern { get; set; } - public string DefaultMessage { get; set; } + public string Message { get; set; } public AddPattern() { diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs index 20a436ba3..da4b7f097 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs @@ -18,6 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands public string Pattern { get; set; } - public string DefaultMessage { get; set; } + public string Message { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 917140969..b44ca76bf 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State protected void On(AppPatternAdded @event) { - Patterns = Patterns.Add(@event.Id, @event.Name, @event.Pattern, @event.DefaultMessage); + Patterns = Patterns.Add(@event.Id, @event.Name, @event.Pattern, @event.Message); } protected void On(AppPatternDeleted @event) @@ -92,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.State protected void On(AppPatternUpdated @event) { - Patterns = Patterns.Update(@event.Id, @event.Name, @event.Pattern, @event.DefaultMessage); + Patterns = Patterns.Update(@event.Id, @event.Name, @event.Pattern, @event.Message); } protected void On(AppLanguageAdded @event) diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs index bbd6fd0be..dd37f4ec7 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs @@ -20,6 +20,6 @@ namespace Squidex.Domain.Apps.Events.Apps public string Pattern { get; set; } - public string DefaultMessage { get; set; } + public string Message { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs index 1f5908b87..c24da6327 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs @@ -20,6 +20,6 @@ namespace Squidex.Domain.Apps.Events.Apps public string Pattern { get; set; } - public string DefaultMessage { get; set; } + public string Message { get; set; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs new file mode 100644 index 000000000..934a07ce5 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -0,0 +1,127 @@ +// ========================================================================== +// AppPatternsController.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using NSwag.Annotations; +using Squidex.Areas.Api.Controllers.Apps.Models; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Reflection; +using Squidex.Pipeline; + +namespace Squidex.Areas.Api.Controllers.Apps +{ + /// + /// Manages and configures app patterns. + /// + [ApiAuthorize] + [MustBeAppDeveloper] + [ApiExceptionFilter] + [AppApi] + [SwaggerTag(nameof(Apps))] + public sealed class AppPatternsController : ApiController + { + public AppPatternsController(ICommandBus commandBus) + : base(commandBus) + { + } + + /// + /// Get app patterns. + /// + /// The name of the app. + /// + /// 200 => Patterns returned. + /// 404 => App not found. + /// + /// + /// Gets all configured regex patterns for the app with the specified name. + /// + [HttpGet] + [Route("apps/{app}/patterns/")] + [ProducesResponseType(typeof(AppPatternDto[]), 200)] + [ApiCosts(1)] + public IActionResult GetPatterns(string app) + { + var response = + App.Patterns.Select(x => SimpleMapper.Map(x.Value, new AppPatternDto { Id = x.Key })).OrderBy(x => x.Name).ToList(); + + return Ok(response); + } + + /// + /// Create a new app patterm. + /// + /// The name of the app. + /// Pattern to be added to the app. + /// + /// 201 => Pattern generated. + /// 404 => App not found. + /// + [HttpPost] + [Route("apps/{app}/patterns/")] + [ProducesResponseType(typeof(AppPatternDto), 201)] + [ApiCosts(1)] + public async Task PostPattern(string app, [FromBody] UpdatePatternDto request) + { + var command = SimpleMapper.Map(request, new AddPattern()); + + await CommandBus.PublishAsync(command); + + return CreatedAtAction(nameof(GetPatterns), new { app }, request); + } + + /// + /// Update an existing app patterm. + /// + /// The name of the app. + /// The id of the pattern to be updated. + /// Pattern to be updated for the app. + /// + /// 204 => Pattern updated. + /// 404 => App not found or pattern not found. + /// + [HttpPut] + [Route("apps/{app}/patterns/{name}")] + [ProducesResponseType(typeof(AppPatternDto), 201)] + [ApiCosts(1)] + public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) + { + var command = SimpleMapper.Map(request, new UpdatePattern { Id = id }); + + await CommandBus.PublishAsync(command); + + return NoContent(); + } + + /// + /// Revoke an app client + /// + /// The name of the app. + /// The id of the pattern to be deleted. + /// + /// 204 => Pattern removed. + /// 404 => App or pattern not found. + /// + /// + /// Schemas using this pattern will still function using the same Regular Expression + /// + [HttpDelete] + [Route("apps/{app}/patterns/{id}/")] + [ApiCosts(1)] + public async Task DeletePattern(string app, Guid id) + { + await CommandBus.PublishAsync(new DeletePattern { Id = id }); + + return NoContent(); + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs new file mode 100644 index 000000000..9fb65d14d --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs @@ -0,0 +1,38 @@ +// ========================================================================== +// AppPatternDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class AppPatternDto + { + /// + /// Identifier for Pattern + /// + public Guid Id { get; set; } + + /// + /// The name of the suggestion. + /// + [Required] + public string Name { get; set; } + + /// + /// The regex pattern. + /// + [Required] + public string Pattern { get; set; } + + /// + /// The regex message. + /// + public string Message { get; set; } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs new file mode 100644 index 000000000..89506dfcc --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// AddPatternDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public class UpdatePatternDto + { + /// + /// The name of the suggestion. + /// + [Required] + public string Name { get; set; } + + /// + /// The regex pattern. + /// + [Required] + public string Pattern { get; set; } + + /// + /// The regex message. + /// + public string Message { get; set; } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index 65bc15461..d192ed65f 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -293,13 +293,13 @@ namespace Squidex.Domain.Apps.Entities.Apps { CreateApp(); - sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" })); + sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); Assert.Single(sut.State.Patterns); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppPatternAdded { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" }) + CreateEvent(new AppPatternAdded { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) ); } @@ -343,13 +343,13 @@ namespace Squidex.Domain.Apps.Entities.Apps CreateApp(); CreatePattern(); - sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" })); + sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); Assert.Single(sut.State.Patterns); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppPatternUpdated { Id = patternId, Name = "Any", Pattern = ".*", DefaultMessage = "Msg" }) + CreateEvent(new AppPatternUpdated { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) ); } From b35e8a509e19027b6600a28dbe709964dd597854 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 13:48:21 +0100 Subject: [PATCH 04/10] Fixes for app pattern. --- .../Apps/AppPattern.cs | 2 +- .../Apps/AppDomainObject.cs | 37 ++++++++++++++----- .../Apps/InitialPatterns.cs | 26 +++++++++++++ .../Controllers/Apps/AppPatternsController.cs | 2 +- .../UI/Models/UIRegexSuggestionDto.cs | 27 -------------- .../Controllers/UI/Models/UISettingsDto.cs | 7 ---- .../Areas/Api/Controllers/UI/UIController.cs | 9 ----- src/Squidex/Config/Domain/WriteServices.cs | 21 +++++++++++ .../Apps/AppCommandMiddlewareTests.cs | 2 +- .../Apps/AppDomainObjectTests.cs | 23 +++++++++--- 10 files changed, 96 insertions(+), 60 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs delete mode 100644 src/Squidex/Areas/Api/Controllers/UI/Models/UIRegexSuggestionDto.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs index cfd0e6e8e..62fec4af0 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.Apps get { return message; } } - public AppPattern(string name, string pattern, string message) + public AppPattern(string name, string pattern, string message = null) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(pattern, nameof(pattern)); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 6aae7ccfe..0a9ca47a3 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -21,15 +21,29 @@ namespace Squidex.Domain.Apps.Entities.Apps { public sealed class AppDomainObject : DomainObjectBase { + private readonly InitialPatterns initialPatterns; + + public AppDomainObject(InitialPatterns initialPatterns) + { + Guard.NotNull(initialPatterns, nameof(initialPatterns)); + + this.initialPatterns = initialPatterns; + } + public AppDomainObject Create(CreateApp command) { ThrowIfCreated(); var appId = new NamedId(command.AppId, command.Name); - RaiseEvent(SimpleMapper.Map(command, CreateInitalEvent(appId))); - RaiseEvent(SimpleMapper.Map(command, CreateInitialOwner(appId, command))); - RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId))); + RaiseEvent(CreateInitalEvent(appId, command.Actor, command.Name)); + RaiseEvent(CreateInitialOwner(appId, command.Actor)); + RaiseEvent(CreateInitialLanguage(appId, command.Actor)); + + foreach (var pattern in initialPatterns) + { + RaiseEvent(CreateInitialPattern(appId, command.Actor, pattern.Key, pattern.Value)); + } return this; } @@ -160,19 +174,24 @@ namespace Squidex.Domain.Apps.Entities.Apps RaiseEvent(Envelope.Create(@event)); } - private static AppCreated CreateInitalEvent(NamedId appId) + private static AppCreated CreateInitalEvent(NamedId appId, RefToken actor, string name) + { + return new AppCreated { AppId = appId, Actor = actor, Name = name }; + } + + private static AppPatternAdded CreateInitialPattern(NamedId appId, RefToken actor, Guid id, AppPattern p) { - return new AppCreated { AppId = appId }; + return new AppPatternAdded { AppId = appId, Actor = actor, Id = id, Name = p.Name, Pattern = p.Pattern, Message = p.Message }; } - private static AppLanguageAdded CreateInitialLanguage(NamedId id) + private static AppLanguageAdded CreateInitialLanguage(NamedId appId, RefToken actor) { - return new AppLanguageAdded { AppId = id, Language = Language.EN }; + return new AppLanguageAdded { AppId = appId, Actor = actor, Language = Language.EN }; } - private static AppContributorAssigned CreateInitialOwner(NamedId id, SquidexCommand command) + private static AppContributorAssigned CreateInitialOwner(NamedId appId, RefToken actor) { - return new AppContributorAssigned { AppId = id, ContributorId = command.Actor.Identifier, Permission = AppContributorPermission.Owner }; + return new AppContributorAssigned { AppId = appId, Actor = actor, ContributorId = actor.Identifier, Permission = AppContributorPermission.Owner }; } private void ThrowIfNotCreated() diff --git a/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs b/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs new file mode 100644 index 000000000..eed1ca02b --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// InitialPatterns.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Squidex.Domain.Apps.Core.Apps; + +namespace Squidex.Domain.Apps.Entities.Apps +{ + public sealed class InitialPatterns : Dictionary + { + public InitialPatterns() + { + } + + public InitialPatterns(Dictionary patterns) + : base(patterns) + { + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 934a07ce5..6dff37ad9 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpGet] [Route("apps/{app}/patterns/")] [ProducesResponseType(typeof(AppPatternDto[]), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public IActionResult GetPatterns(string app) { var response = diff --git a/src/Squidex/Areas/Api/Controllers/UI/Models/UIRegexSuggestionDto.cs b/src/Squidex/Areas/Api/Controllers/UI/Models/UIRegexSuggestionDto.cs deleted file mode 100644 index 363c86758..000000000 --- a/src/Squidex/Areas/Api/Controllers/UI/Models/UIRegexSuggestionDto.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// UIRegexSuggestionDto.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.ComponentModel.DataAnnotations; - -namespace Squidex.Areas.Api.Controllers.UI.Models -{ - public sealed class UIRegexSuggestionDto - { - /// - /// The name of the suggestion. - /// - [Required] - public string Name { get; set; } - - /// - /// The regex pattern. - /// - [Required] - public string Pattern { get; set; } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs b/src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs index c110d7517..7f6306615 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/Models/UISettingsDto.cs @@ -6,19 +6,12 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Squidex.Areas.Api.Controllers.UI.Models { public sealed class UISettingsDto { - /// - /// The regex suggestions. - /// - [Required] - public List RegexSuggestions { get; set; } - /// /// The type of the map control. /// diff --git a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs index 17a3dbc52..441da2aac 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using NSwag.Annotations; @@ -44,13 +42,6 @@ namespace Squidex.Areas.Api.Controllers.UI { var dto = new UISettingsDto { - RegexSuggestions = - uiOptions.RegexSuggestions? - .Where(x => - !string.IsNullOrWhiteSpace(x.Key) && - !string.IsNullOrWhiteSpace(x.Value)) - .Select(x => new UIRegexSuggestionDto { Name = x.Key, Pattern = x.Value }).ToList() - ?? new List(), MapType = uiOptions.Map?.Type ?? "OSM", MapKey = uiOptions.Map?.GoogleMaps?.Key }; diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index 524c3e121..1762078fd 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -6,8 +6,11 @@ // All rights reserved. // ========================================================================== +using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Migrate_01; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; @@ -78,6 +81,24 @@ namespace Squidex.Config.Domain services.AddTransientAs() .AsSelf(); + + services.AddSingleton(c => + { + var config = c.GetRequiredService>(); + + var result = new InitialPatterns(); + + foreach (var pattern in config.Value.RegexSuggestions) + { + if (!string.IsNullOrWhiteSpace(pattern.Key) && + !string.IsNullOrWhiteSpace(pattern.Value)) + { + result[Guid.NewGuid()] = new AppPattern(pattern.Key, pattern.Value); + } + } + + return result; + }); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index b07f64b1a..ef62c44b1 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientName = "client"; private readonly Guid patternId = Guid.NewGuid(); - private readonly AppDomainObject app = new AppDomainObject(); + private readonly AppDomainObject app = new AppDomainObject(new InitialPatterns()); private readonly AppCommandMiddleware sut; protected override Guid Id diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index d192ed65f..2a964de5c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly string clientNewName = "My Client"; private readonly string planId = "premium"; private readonly Guid patternId = Guid.NewGuid(); - private readonly AppDomainObject sut = new AppDomainObject(); + private readonly AppDomainObject sut = new AppDomainObject(new InitialPatterns()); protected override Guid Id { @@ -46,15 +46,28 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public void Create_should_specify_name_and_owner() { - sut.Create(CreateCommand(new CreateApp { Name = AppName, Actor = User, AppId = AppId })); + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); - Assert.Equal(AppName, sut.State.Name); + var initialPatterns = new InitialPatterns + { + { id1, new AppPattern("Number", "[0-9]") }, + { id2, new AppPattern("Numbers", "[0-9]*") } + }; - sut.GetUncomittedEvents() + var app = new AppDomainObject(initialPatterns); + + app.Create(CreateCommand(new CreateApp { Name = AppName, Actor = User, AppId = AppId })); + + Assert.Equal(AppName, app.State.Name); + + app.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppCreated { Name = AppName }), CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Permission = AppContributorPermission.Owner }), - CreateEvent(new AppLanguageAdded { Language = Language.EN }) + CreateEvent(new AppLanguageAdded { Language = Language.EN }), + CreateEvent(new AppPatternAdded { Id = id1, Name = "Number", Pattern = "[0-9]" }), + CreateEvent(new AppPatternAdded { Id = id2, Name = "Numbers", Pattern = "[0-9]*" }) ); } From fecf5bb9833724a4958e19e278bb2d2a74c20a90 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 14:32:34 +0100 Subject: [PATCH 05/10] Id => PatternId Service integrated. --- .../Apps/AppDomainObject.cs | 2 +- .../Apps/Commands/AddPattern.cs | 4 +- .../Apps/Commands/DeletePattern.cs | 2 +- .../Apps/Commands/UpdatePattern.cs | 2 +- .../Apps/Guards/GuardAppPattern.cs | 12 ++--- .../Apps/State/AppState.cs | 6 +-- .../Apps/AppPatternAdded.cs | 2 +- .../Apps/AppPatternDeleted.cs | 2 +- .../Apps/AppPatternUpdated.cs | 2 +- .../Controllers/Apps/AppPatternsController.cs | 9 ++-- .../Controllers/Apps/Models/AppPatternDto.cs | 2 +- .../schemas/pages/schema/field.component.html | 2 +- .../schemas/pages/schema/field.component.ts | 4 ++ .../pages/schema/schema-page.component.html | 1 + .../pages/schema/schema-page.component.ts | 12 ++++- .../types/string-validation.component.html | 7 ++- .../types/string-validation.component.ts | 50 ++++++++++--------- src/Squidex/app/shared/declarations-base.ts | 1 + src/Squidex/app/shared/module.ts | 2 + .../app/shared/services/ui.service.spec.ts | 3 +- src/Squidex/app/shared/services/ui.service.ts | 5 -- .../Apps/AppCommandMiddlewareTests.cs | 8 +-- .../Apps/AppDomainObjectTests.cs | 24 ++++----- .../Apps/Guards/GuardAppPatternsTests.cs | 28 +++++------ 24 files changed, 106 insertions(+), 86 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 0a9ca47a3..03d8e4881 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -181,7 +181,7 @@ namespace Squidex.Domain.Apps.Entities.Apps private static AppPatternAdded CreateInitialPattern(NamedId appId, RefToken actor, Guid id, AppPattern p) { - return new AppPatternAdded { AppId = appId, Actor = actor, Id = id, Name = p.Name, Pattern = p.Pattern, Message = p.Message }; + return new AppPatternAdded { AppId = appId, Actor = actor, PatternId = id, Name = p.Name, Pattern = p.Pattern, Message = p.Message }; } private static AppLanguageAdded CreateInitialLanguage(NamedId appId, RefToken actor) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs index 12df12042..1c438c376 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class AddPattern : AppAggregateCommand { - public Guid Id { get; set; } + public Guid PatternId { get; set; } public string Name { get; set; } @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands public AddPattern() { - Id = Guid.NewGuid(); + PatternId = Guid.NewGuid(); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs index d152f40d0..5fdd10028 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs @@ -12,6 +12,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class DeletePattern : AppAggregateCommand { - public Guid Id { get; set; } + public Guid PatternId { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs index da4b7f097..b025aeb22 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs @@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class UpdatePattern : AppAggregateCommand { - public Guid Id { get; set; } + public Guid PatternId { get; set; } public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs index 3aea26e1c..142cb6061 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPattern.cs @@ -51,9 +51,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - if (!patterns.ContainsKey(command.Id)) + if (!patterns.ContainsKey(command.PatternId)) { - throw new DomainObjectNotFoundException(command.Id.ToString(), typeof(AppPattern)); + throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); } } @@ -61,9 +61,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { Guard.NotNull(command, nameof(command)); - if (!patterns.ContainsKey(command.Id)) + if (!patterns.ContainsKey(command.PatternId)) { - throw new DomainObjectNotFoundException(command.Id.ToString(), typeof(AppPattern)); + throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); } Validate.It(() => "Cannot update pattern.", error => @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards error(new ValidationError("Pattern name can not be empty.", nameof(command.Name))); } - if (patterns.Any(x => x.Key != command.Id && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) + if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) { error(new ValidationError("Pattern name is already assigned.", nameof(command.Name))); } @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards error(new ValidationError("Pattern is not a valid regular expression.", nameof(command.Pattern))); } - if (patterns.Any(x => x.Key != command.Id && x.Value.Pattern == command.Pattern)) + if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) { error(new ValidationError("Pattern already exists.", nameof(command.Pattern))); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index b44ca76bf..b2d404e83 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -82,17 +82,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.State protected void On(AppPatternAdded @event) { - Patterns = Patterns.Add(@event.Id, @event.Name, @event.Pattern, @event.Message); + Patterns = Patterns.Add(@event.PatternId, @event.Name, @event.Pattern, @event.Message); } protected void On(AppPatternDeleted @event) { - Patterns = Patterns.Remove(@event.Id); + Patterns = Patterns.Remove(@event.PatternId); } protected void On(AppPatternUpdated @event) { - Patterns = Patterns.Update(@event.Id, @event.Name, @event.Pattern, @event.Message); + Patterns = Patterns.Update(@event.PatternId, @event.Name, @event.Pattern, @event.Message); } protected void On(AppLanguageAdded @event) diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs index dd37f4ec7..49bc41e10 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternAdded.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Events.Apps [EventType(nameof(AppPatternAdded))] public sealed class AppPatternAdded : AppEvent { - public Guid Id { get; set; } + public Guid PatternId { get; set; } public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs index 0a595e3d4..854980f94 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternDeleted.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Events.Apps [EventType(nameof(AppPatternDeleted))] public sealed class AppPatternDeleted : AppEvent { - public Guid Id { get; set; } + public Guid PatternId { get; set; } public string Name { get; set; } } diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs index c24da6327..b27b5d4e1 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternUpdated.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Events.Apps [EventType(nameof(AppPatternUpdated))] public sealed class AppPatternUpdated : AppEvent { - public Guid Id { get; set; } + public Guid PatternId { get; set; } public string Name { get; set; } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 6dff37ad9..0dfb36706 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -52,7 +52,8 @@ namespace Squidex.Areas.Api.Controllers.Apps public IActionResult GetPatterns(string app) { var response = - App.Patterns.Select(x => SimpleMapper.Map(x.Value, new AppPatternDto { Id = x.Key })).OrderBy(x => x.Name).ToList(); + App.Patterns.Select(x => SimpleMapper.Map(x.Value, new AppPatternDto { PatternId = x.Key })) + .OrderBy(x => x.Name).ToList(); return Ok(response); } @@ -76,6 +77,8 @@ namespace Squidex.Areas.Api.Controllers.Apps await CommandBus.PublishAsync(command); + var response = SimpleMapper.Map(request, new AppPatternDto { PatternId = command.PatternId }); + return CreatedAtAction(nameof(GetPatterns), new { app }, request); } @@ -95,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(1)] public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) { - var command = SimpleMapper.Map(request, new UpdatePattern { Id = id }); + var command = SimpleMapper.Map(request, new UpdatePattern { PatternId = id }); await CommandBus.PublishAsync(command); @@ -119,7 +122,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(1)] public async Task DeletePattern(string app, Guid id) { - await CommandBus.PublishAsync(new DeletePattern { Id = id }); + await CommandBus.PublishAsync(new DeletePattern { PatternId = id }); return NoContent(); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs index 9fb65d14d..ca5b7422e 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs @@ -16,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// /// Identifier for Pattern /// - public Guid Id { get; set; } + public Guid PatternId { get; set; } /// /// The name of the suggestion. diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 07203613f..322930909 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -139,7 +139,7 @@
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.ts b/src/Squidex/app/features/schemas/pages/schema/field.component.ts index 0233f931a..b86ec1086 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.ts @@ -9,6 +9,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { + AppPatternDto, createProperties, fadeAnimation, FieldDto, @@ -31,6 +32,9 @@ export class FieldComponent implements OnInit { @Input() public schemas: SchemaDto[]; + @Input() + public regexSuggestions: AppPatternDto[] = []; + @Output() public locking = new EventEmitter(); diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index 8415c4ac2..30265a7f6 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -62,6 +62,7 @@
; + public regexSuggestions: AppPatternDto[] = []; + public exportSchemaDialog = new ModalView(); public configureScriptsDialog = new ModalView(); @@ -86,7 +90,8 @@ export class SchemaPageComponent implements OnDestroy, OnInit { constructor(public readonly ctx: AppContext, private readonly formBuilder: FormBuilder, private readonly router: Router, - private readonly schemasService: SchemasService + private readonly schemasService: SchemasService, + private readonly appPatternsService: AppPatternsService ) { } @@ -114,6 +119,11 @@ export class SchemaPageComponent implements OnDestroy, OnInit { } private load() { + this.appPatternsService.getPatterns(this.ctx.appName) + .subscribe(dtos => { + this.regexSuggestions = dtos.patterns; + }); + this.schemasService.getSchemas(this.ctx.appName) .subscribe(dtos => { this.schemas = ImmutableArray.of(dtos); diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html index a81166ef9..b98ffd8ac 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html @@ -33,14 +33,17 @@

Suggestions

-
+
{{suggestion.name}}
{{suggestion.pattern}}
+ + {{patternName}} +
-
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts index 772da9f1e..bf00e09dd 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts @@ -10,10 +10,9 @@ import { FormControl, FormGroup } from '@angular/forms'; import { Observable, Subscription } from 'rxjs'; import { + AppPatternDto, ModalView, - StringFieldPropertiesDto, - UIRegexSuggestionDto, - UIService + StringFieldPropertiesDto } from 'shared'; @Component({ @@ -23,7 +22,6 @@ import { }) export class StringValidationComponent implements OnDestroy, OnInit { private patternSubscription: Subscription; - private uiSettingsSubscription: Subscription; @Input() public editForm: FormGroup; @@ -31,22 +29,18 @@ export class StringValidationComponent implements OnDestroy, OnInit { @Input() public properties: StringFieldPropertiesDto; + @Input() + public regexSuggestions: AppPatternDto[] = []; + public showDefaultValue: Observable; - public showPatternMessage: Observable; + public showPatternMessage: boolean; public showPatternSuggestions: Observable; - - public regexSuggestions: UIRegexSuggestionDto[] = []; + public patternName: string; public regexSuggestionsModal = new ModalView(false, false); - constructor( - private readonly uiService: UIService - ) { - } - public ngOnDestroy() { this.patternSubscription.unsubscribe(); - this.uiSettingsSubscription.unsubscribe(); } public ngOnInit() { @@ -71,31 +65,39 @@ export class StringValidationComponent implements OnDestroy, OnInit { .map(x => !x); this.showPatternMessage = - this.editForm.controls['pattern'].valueChanges - .startWith('') - .map(x => x && x.trim().length > 0); + this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim().length > 0; this.showPatternSuggestions = this.editForm.controls['pattern'].valueChanges .startWith('') .map(x => !x || x.trim().length === 0); - this.uiSettingsSubscription = - this.uiService.getSettings() - .subscribe(settings => { - this.regexSuggestions = settings.regexSuggestions; - }); - this.patternSubscription = this.editForm.controls['pattern'].valueChanges .subscribe((value: string) => { if (!value || value.length === 0) { this.editForm.controls['patternMessage'].setValue(undefined); } + this.setPatternName(); }); } - public setPattern(pattern: string) { - this.editForm.controls['pattern'].setValue(pattern); + public setPattern(pattern: AppPatternDto) { + this.patternName = pattern.name; + this.editForm.controls['pattern'].setValue(pattern.pattern); + this.editForm.controls['patternMessage'].setValue(pattern.message); + this.showPatternMessage = true; + } + + private setPatternName() { + const matchingPattern = this.regexSuggestions.find(x => x.pattern === this.editForm.controls['pattern'].value); + + if (matchingPattern) { + this.patternName = matchingPattern.name; + } else if (this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim() !== '') { + this.patternName = 'Advanced'; + } else { + this.patternName = undefined; + } } } \ No newline at end of file diff --git a/src/Squidex/app/shared/declarations-base.ts b/src/Squidex/app/shared/declarations-base.ts index fecc4272e..ece0df32e 100644 --- a/src/Squidex/app/shared/declarations-base.ts +++ b/src/Squidex/app/shared/declarations-base.ts @@ -20,6 +20,7 @@ export * from './interceptors/auth.interceptor'; export * from './services/app-contributors.service'; export * from './services/app-clients.service'; export * from './services/app-languages.service'; +export * from './services/app-patterns.service'; export * from './services/apps-store.service'; export * from './services/apps.service'; export * from './services/assets.service'; diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index 7d60668a8..e6d23c16e 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -17,6 +17,7 @@ import { AppContributorsService, AppLanguagesService, AppMustExistGuard, + AppPatternsService, AppsStoreService, AppsService, AssetComponent, @@ -118,6 +119,7 @@ export class SqxSharedModule { AppContributorsService, AppLanguagesService, AppMustExistGuard, + AppPatternsService, AppsService, AppsStoreService, AssetsService, diff --git a/src/Squidex/app/shared/services/ui.service.spec.ts b/src/Squidex/app/shared/services/ui.service.spec.ts index 182eb502a..1ca53dc26 100644 --- a/src/Squidex/app/shared/services/ui.service.spec.ts +++ b/src/Squidex/app/shared/services/ui.service.spec.ts @@ -41,7 +41,7 @@ describe('UIService', () => { settings1 = result; }); - const response: UISettingsDto = { regexSuggestions: [], mapType: 'OSM', mapKey: '' }; + const response: UISettingsDto = { mapType: 'OSM', mapKey: '' }; const req = httpMock.expectOne('http://service/p/api/ui/settings'); @@ -75,6 +75,5 @@ describe('UIService', () => { req.error(new ErrorEvent('500')); expect(settings).toBeDefined(); - expect(settings!.regexSuggestions).toEqual([]); })); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/services/ui.service.ts b/src/Squidex/app/shared/services/ui.service.ts index 633639291..a1f4841ef 100644 --- a/src/Squidex/app/shared/services/ui.service.ts +++ b/src/Squidex/app/shared/services/ui.service.ts @@ -14,15 +14,10 @@ import 'framework/angular/http-extensions'; import { ApiUrlConfig } from 'framework'; export interface UISettingsDto { - regexSuggestions: UIRegexSuggestionDto[]; mapType: string; mapKey: string; } -export interface UIRegexSuggestionDto { - name: string; pattern: string; -} - @Injectable() export class UIService { private settings: UISettingsDto; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index ef62c44b1..1e141b6c9 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -250,9 +250,9 @@ namespace Squidex.Domain.Apps.Entities.Apps public async Task UpdatePattern_should_update_domain() { CreateApp() - .AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = "." })); + .AddPattern(CreateCommand(new AddPattern { PatternId = patternId, Name = "Any", Pattern = "." })); - var context = CreateContextForCommand(new UpdatePattern { Id = patternId, Name = "Number", Pattern = "[0-9]" }); + var context = CreateContextForCommand(new UpdatePattern { PatternId = patternId, Name = "Number", Pattern = "[0-9]" }); await TestUpdate(app, async _ => { @@ -264,9 +264,9 @@ namespace Squidex.Domain.Apps.Entities.Apps public async Task DeletePattern_should_update_domain_object() { CreateApp() - .AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = "." })); + .AddPattern(CreateCommand(new AddPattern { PatternId = patternId, Name = "Any", Pattern = "." })); - var context = CreateContextForCommand(new DeletePattern { Id = patternId }); + var context = CreateContextForCommand(new DeletePattern { PatternId = patternId }); await TestUpdate(app, async _ => { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index 2a964de5c..b311d4223 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -66,8 +66,8 @@ namespace Squidex.Domain.Apps.Entities.Apps CreateEvent(new AppCreated { Name = AppName }), CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Permission = AppContributorPermission.Owner }), CreateEvent(new AppLanguageAdded { Language = Language.EN }), - CreateEvent(new AppPatternAdded { Id = id1, Name = "Number", Pattern = "[0-9]" }), - CreateEvent(new AppPatternAdded { Id = id2, Name = "Numbers", Pattern = "[0-9]*" }) + CreateEvent(new AppPatternAdded { PatternId = id1, Name = "Number", Pattern = "[0-9]" }), + CreateEvent(new AppPatternAdded { PatternId = id2, Name = "Numbers", Pattern = "[0-9]*" }) ); } @@ -298,7 +298,7 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public void AddPattern_should_throw_exception_if_app_not_created() { - Assert.Throws(() => sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*" }))); + Assert.Throws(() => sut.AddPattern(CreateCommand(new AddPattern { PatternId = patternId, Name = "Any", Pattern = ".*" }))); } [Fact] @@ -306,13 +306,13 @@ namespace Squidex.Domain.Apps.Entities.Apps { CreateApp(); - sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); + sut.AddPattern(CreateCommand(new AddPattern { PatternId = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); Assert.Single(sut.State.Patterns); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppPatternAdded { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) + CreateEvent(new AppPatternAdded { PatternId = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) ); } @@ -323,7 +323,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { sut.DeletePattern(CreateCommand(new DeletePattern { - Id = Guid.NewGuid() + PatternId = Guid.NewGuid() })); }); } @@ -334,20 +334,20 @@ namespace Squidex.Domain.Apps.Entities.Apps CreateApp(); CreatePattern(); - sut.DeletePattern(CreateCommand(new DeletePattern { Id = patternId })); + sut.DeletePattern(CreateCommand(new DeletePattern { PatternId = patternId })); Assert.Empty(sut.State.Patterns); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppPatternDeleted { Id = patternId }) + CreateEvent(new AppPatternDeleted { PatternId = patternId }) ); } [Fact] public void UpdatePattern_should_throw_exception_if_app_not_created() { - Assert.Throws(() => sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*" }))); + Assert.Throws(() => sut.UpdatePattern(CreateCommand(new UpdatePattern { PatternId = patternId, Name = "Any", Pattern = ".*" }))); } [Fact] @@ -356,19 +356,19 @@ namespace Squidex.Domain.Apps.Entities.Apps CreateApp(); CreatePattern(); - sut.UpdatePattern(CreateCommand(new UpdatePattern { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); + sut.UpdatePattern(CreateCommand(new UpdatePattern { PatternId = patternId, Name = "Any", Pattern = ".*", Message = "Msg" })); Assert.Single(sut.State.Patterns); sut.GetUncomittedEvents() .ShouldHaveSameEvents( - CreateEvent(new AppPatternUpdated { Id = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) + CreateEvent(new AppPatternUpdated { PatternId = patternId, Name = "Any", Pattern = ".*", Message = "Msg" }) ); } private void CreatePattern() { - sut.AddPattern(CreateCommand(new AddPattern { Id = patternId, Name = "Name", Pattern = ".*" })); + sut.AddPattern(CreateCommand(new AddPattern { PatternId = patternId, Name = "Name", Pattern = ".*" })); sut.ClearUncommittedEvents(); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs index e6f3202ef..f30de365b 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanAdd_should_throw_exception_if_name_empty() { - var command = new AddPattern { Id = patternId, Name = string.Empty, Pattern = ".*" }; + var command = new AddPattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); } @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanAdd_should_throw_exception_if_pattern_empty() { - var command = new AddPattern { Id = patternId, Name = "any", Pattern = string.Empty }; + var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); } @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanAdd_should_throw_exception_if_pattern_not_valid() { - var command = new AddPattern { Id = patternId, Name = "any", Pattern = "[0-9{1}" }; + var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; Assert.Throws(() => GuardAppPattern.CanAdd(patterns_0, command)); } @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message"); - var command = new AddPattern { Id = patternId, Name = "any", Pattern = ".*" }; + var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; Assert.Throws(() => GuardAppPattern.CanAdd(patterns_1, command)); } @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanAdd_should_not_throw_exception_if_success() { - var command = new AddPattern { Id = patternId, Name = "any", Pattern = ".*" }; + var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; GuardAppPattern.CanAdd(patterns_0, command); } @@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanDelete_should_throw_exception_if_pattern_not_found() { - var command = new DeletePattern { Id = patternId }; + var command = new DeletePattern { PatternId = patternId }; Assert.Throws(() => GuardAppPattern.CanDelete(patterns_0, command)); } @@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - var command = new DeletePattern { Id = patternId }; + var command = new DeletePattern { PatternId = patternId }; GuardAppPattern.CanDelete(patterns_1, command); } @@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - var command = new UpdatePattern { Id = patternId, Name = string.Empty, Pattern = ".*" }; + var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); } @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - var command = new UpdatePattern { Id = patternId, Name = "any", Pattern = string.Empty }; + var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); } @@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - var command = new UpdatePattern { Id = patternId, Name = "any", Pattern = "[0-9{1}" }; + var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_1, command)); } @@ -129,7 +129,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); - var command = new UpdatePattern { Id = id2, Name = "Pattern1", Pattern = "[0-4]" }; + var command = new UpdatePattern { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_2, command)); } @@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards var patterns_1 = patterns_0.Add(id1, "Pattern1", "[0-5]", "Message"); var patterns_2 = patterns_1.Add(id2, "Pattern2", "[0-4]", "Message"); - var command = new UpdatePattern { Id = id2, Name = "Pattern2", Pattern = "[0-5]" }; + var command = new UpdatePattern { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_2, command)); } @@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards [Fact] public void CanUpdate_should_throw_exception_if_pattern_does_not_exists() { - var command = new UpdatePattern { Id = patternId, Name = "Pattern1", Pattern = ".*" }; + var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; Assert.Throws(() => GuardAppPattern.CanUpdate(patterns_0, command)); } @@ -161,7 +161,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - var command = new UpdatePattern { Id = patternId, Name = "Pattern1", Pattern = ".*" }; + var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; GuardAppPattern.CanUpdate(patterns_1, command); } From 71f9051ffe5d64ed0a8d4124cf0829f42a0ec3f4 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 14:33:06 +0100 Subject: [PATCH 06/10] Service added --- .../services/app-patterns.service.spec.ts | 172 ++++++++++++++++++ .../shared/services/app-patterns.service.ts | 127 +++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 src/Squidex/app/shared/services/app-patterns.service.spec.ts create mode 100644 src/Squidex/app/shared/services/app-patterns.service.ts diff --git a/src/Squidex/app/shared/services/app-patterns.service.spec.ts b/src/Squidex/app/shared/services/app-patterns.service.spec.ts new file mode 100644 index 000000000..b5fa92f91 --- /dev/null +++ b/src/Squidex/app/shared/services/app-patterns.service.spec.ts @@ -0,0 +1,172 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { inject, TestBed } from '@angular/core/testing'; + +import { + ApiUrlConfig, + AppPatternDto, + AppPatternsDto, + AppPatternsService, + UpdatePatternDto, + Version +} from './../'; + +describe('ApppatternsDto', () => { + const pattern1 = new AppPatternDto('1', 'Any', '.*', 'Message1'); + const pattern2 = new AppPatternDto('2', 'Number', '[0-9]', 'Message2'); + const pattern2_new = new AppPatternDto('2', 'Numbers', '[0-9]*', 'Message2_1'); + const version = new Version('1'); + const newVersion = new Version('2'); + + it('should update patterns when adding pattern', () => { + const patterns_1 = new AppPatternsDto([pattern1], version); + const patterns_2 = patterns_1.addPattern(pattern2, newVersion); + + expect(patterns_2.patterns).toEqual([pattern1, pattern2]); + expect(patterns_2.version).toEqual(newVersion); + }); + + it('should update patterns when removing pattern', () => { + const patterns_1 = new AppPatternsDto([pattern1, pattern2], version); + const patterns_2 = patterns_1.removePattern(pattern1, newVersion); + + expect(patterns_2.patterns).toEqual([pattern2]); + expect(patterns_2.version).toEqual(newVersion); + }); + + it('should update patterns when updating pattern', () => { + const patterns_1 = new AppPatternsDto([pattern1, pattern2], version); + const patterns_2 = patterns_1.updatePattern(pattern2_new, newVersion); + + expect(patterns_2.patterns).toEqual([pattern1, pattern2_new]); + expect(patterns_2.version).toEqual(newVersion); + }); +}); + +describe('AppPatternDto', () => { + it('should update properties when updating', () => { + const pattern_1 = new AppPatternDto('1', 'Number', '[0-9]', 'Message1'); + const pattern_2 = pattern_1.update(new UpdatePatternDto('Numbers', '[0-9]*', 'Message2')); + + expect(pattern_2.name).toBe('Numbers'); + expect(pattern_2.pattern).toBe('[0-9]*'); + expect(pattern_2.message).toBe('Message2'); + }); +}); + +describe('AppPatternsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ], + providers: [ + AppPatternsService, + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') } + ] + }); + }); + + afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { + httpMock.verify(); + })); + + it('should make get request to get patterns', + inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { + + let patterns: AppPatternsDto | null = null; + + patternService.getPatterns('my-app').subscribe(result => { + patterns = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush([ + { + patternId: '1', + pattern: '[0-9]', + name: 'Number', + message: 'Message1' + }, { + patternId: '2', + pattern: '[0-9]*', + name: 'Numbers', + message: 'Message2' + } + ], { + headers: { + etag: '2' + } + }); + + expect(patterns).toEqual( + new AppPatternsDto([ + new AppPatternDto('1', 'Number', '[0-9]', 'Message1'), + new AppPatternDto('2', 'Numbers', '[0-9]*', 'Message2') + ], new Version('2'))); + })); + + it('should make post request to add pattern', + inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { + + const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1'); + + let pattern: AppPatternDto | null = null; + + patternService.postPattern('my-app', dto, new Version()).subscribe(result => { + pattern = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns'); + + expect(req.request.method).toEqual('POST'); + expect(req.request.headers.get('E-Tag')).toBeNull(); + + req.flush({ + patternId: '1', + pattern: '[0-9]', + name: 'Number', + message: 'Message1' + }); + + expect(pattern).toEqual(new AppPatternDto('1', 'Number', '[0-9]', 'Message1')); + })); + + it('should make put request to update pattern', + inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { + + const dto = new UpdatePatternDto('Number', '[0-9]', 'Message1'); + + patternService.updatePattern('my-app', '1', dto, new Version()).subscribe(); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('E-Tag')).toBeNull(); + + req.flush({}); + })); + + it('should make delete request to remove pattern', + inject([AppPatternsService, HttpTestingController], (patternService: AppPatternsService, httpMock: HttpTestingController) => { + + patternService.deletePattern('my-app', '1', new Version('1')).subscribe(); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); + + expect(req.request.method).toEqual('DELETE'); + expect(req.request.headers.get('If-Match')).toEqual(new Version('1').value); + + req.flush({}); + })); +}); \ No newline at end of file diff --git a/src/Squidex/app/shared/services/app-patterns.service.ts b/src/Squidex/app/shared/services/app-patterns.service.ts new file mode 100644 index 000000000..9585a86c7 --- /dev/null +++ b/src/Squidex/app/shared/services/app-patterns.service.ts @@ -0,0 +1,127 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +import 'framework/angular/http-extensions'; + +import { + ApiUrlConfig, + HTTP, + Version +} from 'framework'; + +export class AppPatternsDto { + constructor( + public readonly patterns: AppPatternDto[], + public readonly version: Version + ) { + } + + public addPattern(pattern: AppPatternDto, version: Version) { + return new AppPatternsDto([...this.patterns, pattern], version); + } + + public updatePattern(pattern: AppPatternDto, version: Version) { + return new AppPatternsDto(this.patterns.map(p => p.patternId === pattern.patternId ? pattern : p), version); + } + + public removePattern(pattern: AppPatternDto, version: Version) { + return new AppPatternsDto(this.patterns.filter(c => c.patternId !== pattern.patternId), version); + } +} + +export class AppPatternDto { + constructor( + public readonly patternId: string, + public readonly name: string, + public readonly pattern: string, + public readonly message: string + ) { + } + + public update(update: UpdatePatternDto) { + return new AppPatternDto( + this.patternId, + update.name, + update.pattern, + update.message); + } +} + +export class UpdatePatternDto { + constructor( + public readonly name: string, + public readonly pattern: string, + public readonly message: string + ) { + } +} + + +@Injectable() +export class AppPatternsService { + constructor( + private readonly http: HttpClient, + private readonly apiUrl: ApiUrlConfig + ) { + } + + public getPatterns(appName: string): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`); + + return HTTP.getVersioned(this.http, url) + .map(response => { + const body = response.payload.body; + + const items: any[] = body; + + return new AppPatternsDto( + items.map(item => { + return new AppPatternDto( + item.patternId, + item.name, + item.pattern, + item.message); + }), + response.version); + }) + .pretifyError('Failed to add pattern. Please reload.'); + } + + public postPattern(appName: string, pattern: UpdatePatternDto, version: Version): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`); + + return HTTP.postVersioned(this.http, url, pattern, version) + .map(response => { + const body = response.payload.body; + + return new AppPatternDto( + body.patternId, + body.name, + body.pattern, + body.message); + }) + .pretifyError('Failed to add pattern. Please reload.'); + } + + public updatePattern(appName: string, id: string, pattern: UpdatePatternDto, version: Version): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); + + return HTTP.putVersioned(this.http, url, pattern, version) + .pretifyError('Failed to update pattern. Please reload.'); + } + + public deletePattern(appName: string, id: string, version: Version): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); + + return HTTP.deleteVersioned(this.http, url, version) + .pretifyError('Failed to remove pattern. Please reload.'); + } +} \ No newline at end of file From afe55e35497b3443e7d1005dc4dc88dc7f648fd9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 17 Dec 2017 16:04:41 +0100 Subject: [PATCH 07/10] App UI integrated. --- .../Apps/AppPattern.cs | 4 +- .../Apps/Json/JsonAppPattern.cs | 3 - .../Apps/AppCommandMiddleware.cs | 6 +- .../Migrations/MongoMigrationStatus.cs | 2 +- .../Controllers/Apps/AppPatternsController.cs | 2 +- .../Config/Domain/SerializationServices.cs | 1 + .../types/string-validation.component.ts | 2 + .../app/features/settings/declarations.ts | 2 + src/Squidex/app/features/settings/module.ts | 24 +++++ .../pages/patterns/pattern.component.html | 45 +++++++++ .../pages/patterns/pattern.component.scss | 13 +++ .../pages/patterns/pattern.component.ts | 95 +++++++++++++++++++ .../patterns/patterns-page.component.html | 41 ++++++++ .../patterns/patterns-page.component.scss | 2 + .../pages/patterns/patterns-page.component.ts | 87 +++++++++++++++++ .../settings/settings-area.component.html | 6 ++ .../services/app-patterns.service.spec.ts | 18 ++-- .../shared/services/app-patterns.service.ts | 11 ++- tools/Migrate_01/Migration01.cs | 41 +------- 19 files changed, 344 insertions(+), 61 deletions(-) create mode 100644 src/Squidex/app/features/settings/pages/patterns/pattern.component.html create mode 100644 src/Squidex/app/features/settings/pages/patterns/pattern.component.scss create mode 100644 src/Squidex/app/features/settings/pages/patterns/pattern.component.ts create mode 100644 src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html create mode 100644 src/Squidex/app/features/settings/pages/patterns/patterns-page.component.scss create mode 100644 src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs index 62fec4af0..ee64fc208 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPattern.cs @@ -43,9 +43,9 @@ namespace Squidex.Domain.Apps.Core.Apps } [Pure] - public AppPattern Update(string name, string pattern, string defaultMessage) + public AppPattern Update(string name, string pattern, string message) { - return new AppPattern(name, pattern, defaultMessage); + return new AppPattern(name, pattern, message); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs index 9c52a8198..57364a197 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppPattern.cs @@ -13,9 +13,6 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { public class JsonAppPattern { - [JsonProperty] - public Guid Id { get; set; } - [JsonProperty] public string Name { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index ccdf24331..72d86aba2 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Apps protected Task On(AddPattern command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { GuardAppPattern.CanAdd(a.State.Patterns, command); @@ -150,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Apps protected Task On(DeletePattern command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { GuardAppPattern.CanDelete(a.State.Patterns, command); @@ -160,7 +160,7 @@ namespace Squidex.Domain.Apps.Entities.Apps protected async Task On(UpdatePattern command, CommandContext context) { - await handler.UpdateAsync(context, a => + await handler.UpdateSyncedAsync(context, a => { GuardAppPattern.CanUpdate(a.State.Patterns, command); diff --git a/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs index eb0ab586a..f70bf2e71 100644 --- a/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs +++ b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs @@ -40,7 +40,7 @@ namespace Squidex.Infrastructure.Migrations await Collection.FindOneAndUpdateAsync(x => x.Id == DefaultId, Update .Set(x => x.IsLocked, true) - .Set(x => x.Version, 0), + .SetOnInsert(x => x.Version, 0), UpsertFind); return entity == null || entity.IsLocked == false; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 0dfb36706..846353eaf 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -93,7 +93,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// 404 => App not found or pattern not found. /// [HttpPut] - [Route("apps/{app}/patterns/{name}")] + [Route("apps/{app}/patterns/{id}/")] [ProducesResponseType(typeof(AppPatternDto), 201)] [ApiCosts(1)] public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index bbbd00046..90ab4167c 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -43,6 +43,7 @@ namespace Squidex.Config.Domain settings.ContractResolver = new ConverterContractResolver( new AppClientsConverter(), new AppContributorsConverter(), + new AppPatternsConverter(), new ClaimsPrincipalConverter(), new InstantConverter(), new LanguageConverter(), diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts index bf00e09dd..4c0b8750a 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts @@ -80,6 +80,8 @@ export class StringValidationComponent implements OnDestroy, OnInit { } this.setPatternName(); }); + + this.setPatternName(); } public setPattern(pattern: AppPatternDto) { diff --git a/src/Squidex/app/features/settings/declarations.ts b/src/Squidex/app/features/settings/declarations.ts index ee83aef2c..4bbde478f 100644 --- a/src/Squidex/app/features/settings/declarations.ts +++ b/src/Squidex/app/features/settings/declarations.ts @@ -10,6 +10,8 @@ export * from './pages/clients/clients-page.component'; export * from './pages/contributors/contributors-page.component'; export * from './pages/languages/language.component'; export * from './pages/languages/languages-page.component'; +export * from './pages/patterns/pattern.component'; +export * from './pages/patterns/patterns-page.component'; export * from './pages/plans/plans-page.component'; export * from './settings-area.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/settings/module.ts b/src/Squidex/app/features/settings/module.ts index e2be986c4..281a4489f 100644 --- a/src/Squidex/app/features/settings/module.ts +++ b/src/Squidex/app/features/settings/module.ts @@ -22,6 +22,8 @@ import { ContributorsPageComponent, LanguageComponent, LanguagesPageComponent, + PatternComponent, + PatternsPageComponent, PlansPageComponent, SettingsAreaComponent } from './declarations'; @@ -97,6 +99,26 @@ const routes: Routes = [ } } ] + }, + { + path: 'patterns', + component: PatternsPageComponent, + children: [ + { + path: 'history', + component: HistoryComponent, + data: { + channel: 'settings.patterns' + } + }, + { + path: 'help', + component: HelpComponent, + data: { + helpPage: '05-integrated/patterns' + } + } + ] } ] } @@ -115,6 +137,8 @@ const routes: Routes = [ ContributorsPageComponent, LanguageComponent, LanguagesPageComponent, + PatternComponent, + PatternsPageComponent, PlansPageComponent, SettingsAreaComponent ] diff --git a/src/Squidex/app/features/settings/pages/patterns/pattern.component.html b/src/Squidex/app/features/settings/pages/patterns/pattern.component.html new file mode 100644 index 000000000..51913da2b --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/pattern.component.html @@ -0,0 +1,45 @@ +
+
+
+ + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/patterns/pattern.component.scss b/src/Squidex/app/features/settings/pages/patterns/pattern.component.scss new file mode 100644 index 000000000..d5c22dda1 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/pattern.component.scss @@ -0,0 +1,13 @@ +@import '_vars'; +@import '_mixins'; + +.col-options { + min-width: 7.5rem; + max-width: 7.5rem; +} + +.col-name, +.col-message { + min-width: 10rem; + max-width: 10rem; +} \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts new file mode 100644 index 000000000..41d612573 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts @@ -0,0 +1,95 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, Validators } from '@angular/forms'; + +import { + AppPatternDto, + fadeAnimation, + ValidatorsEx, + UpdatePatternDto +} from 'shared'; + +@Component({ + selector: 'sqx-pattern', + styleUrls: ['./pattern.component.scss'], + templateUrl: './pattern.component.html', + animations: [ + fadeAnimation + ] +}) +export class PatternComponent implements OnInit { + @Input() + public isNew = false; + + @Input() + public pattern: AppPatternDto; + + @Output() + public removing = new EventEmitter(); + + @Output() + public updating = new EventEmitter(); + + public editFormSubmitted = false; + public editForm = + this.formBuilder.group({ + name: [ + '', + [ + Validators.required, + Validators.maxLength(100), + ValidatorsEx.pattern('[A-z0-9]+[A-z0-9\- ]*[A-z0-9]', 'Name can only contain letters, numbers, dashes and spaces.') + ] + ], + pattern: [ + '', + [ + Validators.required + ] + ], + message: [ + '', + [ + Validators.maxLength(1000) + ] + ] + }); + + constructor( + private readonly formBuilder: FormBuilder + ) { + } + + public ngOnInit() { + const pattern = this.pattern; + + if (pattern) { + this.editForm.setValue({ name: pattern.name, pattern: pattern.pattern, message: pattern.message || '' }); + } + } + + public cancel() { + this.editFormSubmitted = false; + this.editForm.reset(); + } + + public save() { + this.editFormSubmitted = true; + + if (this.editForm.valid) { + const requestDto = new UpdatePatternDto( + this.editForm.controls['name'].value, + this.editForm.controls['pattern'].value, + this.editForm.controls['message'].value); + + this.updating.emit(requestDto); + } + } +} + diff --git a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html new file mode 100644 index 000000000..433c42aa8 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html @@ -0,0 +1,41 @@ + + + +
+
+

Patterns

+
+ + + + +
+ +
+
+
+
+ + +
+ + +
+
+ + + +
+
+ + \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.scss b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts new file mode 100644 index 000000000..d7e9868c2 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts @@ -0,0 +1,87 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, OnInit } from '@angular/core'; + +import { + AppContext, + AppPatternDto, + AppPatternsDto, + AppPatternsService, + HistoryChannelUpdated, + UpdatePatternDto +} from 'shared'; + +@Component({ + selector: 'sqx-patterns-page', + styleUrls: ['./patterns-page.component.scss'], + templateUrl: './patterns-page.component.html', + providers: [ + AppContext + ] +}) +export class PatternsPageComponent implements OnInit { + public appPatterns: AppPatternsDto; + + constructor(public readonly ctx: AppContext, + private readonly appPatternsService: AppPatternsService + ) { + } + + public ngOnInit() { + this.load(); + } + + public load() { + this.appPatternsService.getPatterns(this.ctx.appName).retry(2) + .subscribe(dtos => { + this.updatePatterns(dtos); + }, error => { + this.ctx.notifyError(error); + }); + } + + public addPattern(pattern: AppPatternDto) { + const requestDto = new UpdatePatternDto(pattern.name, pattern.pattern, pattern.message); + + this.appPatternsService.postPattern(this.ctx.appName, requestDto, this.appPatterns.version) + .subscribe(dto => { + this.updatePatterns(this.appPatterns.addPattern(dto.payload, dto.version)); + }, error => { + this.ctx.notifyError(error); + }); + } + + public updatePattern(pattern: AppPatternDto, update: UpdatePatternDto) { + this.appPatternsService.putPattern(this.ctx.appName, pattern.patternId, update, this.appPatterns.version) + .subscribe(dto => { + this.updatePatterns(this.appPatterns.updatePattern(pattern.update(update), dto.version)); + }, error => { + this.ctx.notifyError(error); + }); + } + + public removePattern(pattern: AppPatternDto) { + this.appPatternsService.deletePattern(this.ctx.appName, pattern.patternId, this.appPatterns.version) + .subscribe(dto => { + this.updatePatterns(this.appPatterns.deletePattern(pattern, dto.version)); + }, error => { + this.ctx.notifyError(error); + }); + } + + private updatePatterns(patterns: AppPatternsDto) { + this.appPatterns = + new AppPatternsDto( + patterns.patterns.sort((a, b) => { + return a.name.localeCompare(b.name); + }), + patterns.version); + + this.ctx.bus.emit(new HistoryChannelUpdated()); + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/settings/settings-area.component.html b/src/Squidex/app/features/settings/settings-area.component.html index 7dd011dad..65954946d 100644 --- a/src/Squidex/app/features/settings/settings-area.component.html +++ b/src/Squidex/app/features/settings/settings-area.component.html @@ -32,6 +32,12 @@ +