diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs index cb9e13d3d..b86f572af 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; +using System.Linq; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; @@ -26,6 +27,16 @@ namespace Squidex.Domain.Apps.Core.Apps { } + public static AppPatterns Create(IEnumerable patterns) + { + if (patterns == null || !patterns.Any()) + { + return Empty; + } + + return new AppPatterns(patterns.Select(x => new KeyValuePair(Guid.NewGuid(), x)).ToArray()); + } + [Pure] public AppPatterns Remove(Guid id) { diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs new file mode 100644 index 000000000..8f94a80cf --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Apps +{ + public static class AppExtensions + { + public static NamedId NamedId(this IAppEntity app) + { + return new NamedId(app.Id, app.Name); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs index 3645192a0..55c36460c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; @@ -157,28 +158,12 @@ namespace Squidex.Domain.Apps.Entities.Apps UpdateRole(c); }); - case AddPattern addPattern: - return UpdateAsync(addPattern, c => + case ConfigurePatterns configurePatterns: + return UpdateAsync(configurePatterns, c => { - GuardAppPatterns.CanAdd(Snapshot.Patterns, c); + GuardAppPatterns.CanConfigure(c); - AddPattern(c); - }); - - case DeletePattern deletePattern: - return UpdateAsync(deletePattern, c => - { - GuardAppPatterns.CanDelete(Snapshot.Patterns, c); - - DeletePattern(c); - }); - - case UpdatePattern updatePattern: - return UpdateAsync(updatePattern, c => - { - GuardAppPatterns.CanUpdate(Snapshot.Patterns, c); - - UpdatePattern(c); + ConfigurePatterns(c); }); case ChangePlan changePlan: @@ -194,7 +179,7 @@ namespace Squidex.Domain.Apps.Entities.Apps } else { - var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, c.PlanId); + var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId); switch (result) { @@ -213,7 +198,7 @@ namespace Squidex.Domain.Apps.Entities.Apps case ArchiveApp archiveApp: return UpdateAsync(archiveApp, async c => { - await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, null); + await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null); ArchiveApp(c); }); @@ -234,16 +219,12 @@ namespace Squidex.Domain.Apps.Entities.Apps var events = new List { - CreateInitalEvent(command.Name), + CreateInitialEvent(command.Name), + CreateInitialLanguage(), CreateInitialOwner(command.Actor), - CreateInitialLanguage() + CreateInitialPatterns(initialPatterns) }; - foreach (var pattern in initialPatterns) - { - events.Add(CreateInitialPattern(pattern.Key, pattern.Value)); - } - foreach (var @event in events) { @event.Actor = command.Actor; @@ -311,19 +292,9 @@ namespace Squidex.Domain.Apps.Entities.Apps RaiseEvent(SimpleMapper.Map(command, new AppPlanReset())); } - public void AddPattern(AddPattern command) + public void ConfigurePatterns(ConfigurePatterns command) { - RaiseEvent(SimpleMapper.Map(command, new AppPatternAdded())); - } - - public void DeletePattern(DeletePattern command) - { - RaiseEvent(SimpleMapper.Map(command, new AppPatternDeleted())); - } - - public void UpdatePattern(UpdatePattern command) - { - RaiseEvent(SimpleMapper.Map(command, new AppPatternUpdated())); + RaiseEvent(SimpleMapper.Map(command, CreatePatterns(command))); } public void AddRole(AddRole command) @@ -358,22 +329,17 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (@event.AppId == null) { - @event.AppId = NamedId.Of(Snapshot.Id, Snapshot.Name); + @event.AppId = Snapshot.NamedId(); } RaiseEvent(Envelope.Create(@event)); } - private static AppCreated CreateInitalEvent(string name) + private static AppCreated CreateInitialEvent(string name) { return new AppCreated { Name = name }; } - private static AppPatternAdded CreateInitialPattern(Guid id, AppPattern pattern) - { - return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message }; - } - private static AppLanguageAdded CreateInitialLanguage() { return new AppLanguageAdded { Language = Language.EN }; @@ -384,6 +350,21 @@ namespace Squidex.Domain.Apps.Entities.Apps return new AppContributorAssigned { ContributorId = actor.Identifier, Role = Role.Owner }; } + private static AppPatternsConfigured CreateInitialPatterns(InitialPatterns patterns) + { + return new AppPatternsConfigured { Patterns = patterns.ToArray() }; + } + + private static AppPatternsConfigured CreatePatterns(ConfigurePatterns command) + { + return new AppPatternsConfigured { Patterns = command.Patterns?.Select(Convert).ToArray() }; + } + + private static AppPattern Convert(UpsertAppPattern source) + { + return new AppPattern(source.Name, source.Pattern, source.Message); + } + public Task> GetStateAsync() { return J.AsTask(Snapshot); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs deleted file mode 100644 index 30873adbb..000000000 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Domain.Apps.Entities.Apps.Commands -{ - public sealed class AddPattern : AppCommand - { - public Guid PatternId { get; set; } - - public string Name { get; set; } - - public string Pattern { get; set; } - - public string Message { get; set; } - - public AddPattern() - { - PatternId = Guid.NewGuid(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ConfigurePatterns.cs similarity index 70% rename from src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/ConfigurePatterns.cs index 199bff83c..b0746007b 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/DeletePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ConfigurePatterns.cs @@ -1,16 +1,14 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; - namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class DeletePattern : AppCommand + public sealed class ConfigurePatterns : AppCommand { - public Guid PatternId { get; set; } + public UpsertAppPattern[] Patterns { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpsertAppPattern.cs similarity index 76% rename from src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/UpsertAppPattern.cs index 415856189..5b1acdde5 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdatePattern.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpsertAppPattern.cs @@ -1,18 +1,14 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; - namespace Squidex.Domain.Apps.Entities.Apps.Commands { - public sealed class UpdatePattern : AppCommand + public sealed class UpsertAppPattern { - public Guid PatternId { get; set; } - public string Name { get; set; } public string Pattern { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs index 685441e53..0a8011a40 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs @@ -4,9 +4,9 @@ // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== + using System; using System.Linq; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; @@ -14,88 +14,64 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppPatterns { - public static void CanAdd(AppPatterns patterns, AddPattern command) + public static void CanConfigure(ConfigurePatterns command) { Guard.NotNull(command, nameof(command)); - Validate.It(() => "Cannot add pattern.", e => + Validate.It(() => "Cannot configure patterns.", e => { - if (command.PatternId == Guid.Empty) + if (command.Patterns?.Length > 0) { - e(Not.Defined("Id"), nameof(command.PatternId)); - } + var patternIndex = 0; + var patternPrefix = string.Empty; - if (string.IsNullOrWhiteSpace(command.Name)) - { - e(Not.Defined("Name"), nameof(command.Name)); - } + foreach (var pattern in command.Patterns) + { + patternIndex++; + patternPrefix = $"{nameof(command.Patterns)}[{patternIndex}]"; - if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) - { - e("A pattern with the same name already exists."); - } + ValidatePattern(pattern, patternPrefix, e); + } - if (string.IsNullOrWhiteSpace(command.Pattern)) - { - e(Not.Defined("Pattern"), nameof(command.Pattern)); - } - else if (!command.Pattern.IsValidRegex()) - { - e(Not.Valid("Pattern"), nameof(command.Pattern)); - } + var validNames = command.Patterns.Select(p => p?.Name).Where(p => !string.IsNullOrWhiteSpace(p)); - if (patterns.Values.Any(x => x.Pattern == command.Pattern)) - { - e("This pattern already exists but with another name."); - } - }); - } + if (validNames.Count() != validNames.Distinct(StringComparer.OrdinalIgnoreCase).Count()) + { + e("Two patterns with the same name exist.", nameof(command.Patterns)); + } - public static void CanDelete(AppPatterns patterns, DeletePattern command) - { - Guard.NotNull(command, nameof(command)); + var validPatterns = command.Patterns.Select(p => p?.Pattern).Where(p => !string.IsNullOrWhiteSpace(p)); - if (!patterns.ContainsKey(command.PatternId)) - { - throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); - } + if (validPatterns.Count() != validPatterns.Distinct().Count()) + { + e("Two patterns with the same expression exist.", nameof(command.Patterns)); + } + } + }); } - public static void CanUpdate(AppPatterns patterns, UpdatePattern command) + private static void ValidatePattern(UpsertAppPattern pattern, string prefix, AddValidation e) { - Guard.NotNull(command, nameof(command)); - - if (!patterns.ContainsKey(command.PatternId)) + if (pattern == null) { - throw new DomainObjectNotFoundException(command.PatternId.ToString(), typeof(AppPattern)); + e(Not.Defined("Pattern"), prefix); } - - Validate.It(() => "Cannot update pattern.", e => + else { - if (string.IsNullOrWhiteSpace(command.Name)) + if (string.IsNullOrWhiteSpace(pattern.Name)) { - e(Not.Defined("Name"), nameof(command.Name)); + e(Not.Defined("Name"), $"{prefix}.{nameof(pattern.Name)}"); } - if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) + if (string.IsNullOrWhiteSpace(pattern.Pattern)) { - e("A pattern with the same name already exists."); + e(Not.Defined("Expression"), $"{prefix}.{nameof(pattern.Pattern)}"); } - - if (string.IsNullOrWhiteSpace(command.Pattern)) + else if (!pattern.Pattern.IsValidRegex()) { - e(Not.Defined("Pattern"), nameof(command.Pattern)); + e(Not.Valid("Expression"), $"{prefix}.{nameof(pattern.Pattern)}"); } - else if (!command.Pattern.IsValidRegex()) - { - e(Not.Valid("Pattern"), nameof(command.Pattern)); - } - - if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) - { - e("This pattern already exists but with another name."); - } - }); + } } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs b/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs index d8da459ed..ec55fac44 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/InitialPatterns.cs @@ -5,19 +5,18 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Entities.Apps { - public sealed class InitialPatterns : Dictionary + public sealed class InitialPatterns : List { public InitialPatterns() { } - public InitialPatterns(Dictionary patterns) + public InitialPatterns(IEnumerable patterns) : base(patterns) { } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs index 89c6342cd..933a11ddf 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Services { @@ -14,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services { bool HasPortal { get; } - Task ChangePlanAsync(string userId, Guid appId, string appName, string planId); + Task ChangePlanAsync(string userId, NamedId appId, string planId); Task GetPortalLinkAsync(string userId); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs index 8e968ccc7..b8c1f46ef 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations { @@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations get { return false; } } - public Task ChangePlanAsync(string userId, Guid appId, string appName, string planId) + public Task ChangePlanAsync(string userId, NamedId appId, string planId) { return Task.FromResult(new PlanResetResult()); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 4f31dbdc0..162ecc75c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -92,6 +92,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.State Clients = Clients.Revoke(@event.Id); } + protected void On(AppPatternsConfigured @event) + { + Patterns = AppPatterns.Create(@event.Patterns); + } + protected void On(AppPatternAdded @event) { Patterns = Patterns.Add(@event.PatternId, @event.Name, @event.Pattern, @event.Message); diff --git a/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs b/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs new file mode 100644 index 000000000..b5c78cd43 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities +{ + public static class EntityExtensions + { + public static NamedId NamedId(this IAppEntity entity) + { + return new NamedId(entity.Id, entity.Name); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index ab4851731..4e937c96e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -142,49 +142,73 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards fieldIndex++; fieldPrefix = $"Fields[{fieldIndex}]"; - if (!field.Partitioning.IsValidPartitioning()) - { - e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}"); - } + ValidateRootField(field, fieldPrefix, e); + } - ValidateField(field, fieldPrefix, e); + if (command.Fields.Select(x => x?.Name).Distinct().Count() != command.Fields.Count) + { + e("Fields cannot have duplicate names.", nameof(command.Fields)); + } + } + } - if (field.Nested?.Count > 0) - { - if (field.Properties is ArrayFieldProperties) - { - var nestedIndex = 0; - var nestedPrefix = string.Empty; + private static void ValidateRootField(UpsertSchemaField field, string prefix, AddValidation e) + { + if (field == null) + { + e(Not.Defined("Field"), prefix); + } + else + { + if (!field.Partitioning.IsValidPartitioning()) + { + e(Not.Valid("Partitioning"), $"{prefix}.{nameof(field.Partitioning)}"); + } - foreach (var nestedField in field.Nested) - { - nestedIndex++; - nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]"; + ValidateField(field, prefix, e); - if (nestedField.Properties is ArrayFieldProperties) - { - e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}"); - } + if (field.Nested?.Count > 0) + { + if (field.Properties is ArrayFieldProperties) + { + var nestedIndex = 0; + var nestedPrefix = string.Empty; - ValidateField(nestedField, nestedPrefix, e); - } - } - else if (field.Nested.Count > 0) + foreach (var nestedField in field.Nested) { - e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}"); - } + nestedIndex++; + nestedPrefix = $"{prefix}.Nested[{nestedIndex}]"; - if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count) - { - e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested"); + ValidateNestedField(nestedField, nestedPrefix, e); } } + else if (field.Nested.Count > 0) + { + e("Only array fields can have nested fields.", $"{prefix}.{nameof(field.Partitioning)}"); + } + + if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count) + { + e("Fields cannot have duplicate names.", $"{prefix}.Nested"); + } } + } + } - if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count) + private static void ValidateNestedField(UpsertSchemaNestedField nestedField, string prefix, AddValidation e) + { + if (nestedField == null) + { + e(Not.Defined("Field"), prefix); + } + else + { + if (nestedField.Properties is ArrayFieldProperties) { - e("Fields cannot have duplicate names.", nameof(command.Fields)); + e("Nested field cannot be array fields.", $"{prefix}.{nameof(nestedField.Properties)}"); } + + ValidateField(nestedField, prefix, e); } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs index 5905b66fd..44e96fa7f 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs @@ -321,7 +321,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas { if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field)) { - return NamedId.Of(field.Id, field.Name); + return field.NamedId(); } return null; @@ -333,13 +333,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas { if (Snapshot.SchemaDef.FieldsById.TryGetValue(pc.ParentFieldId.Value, out var field)) { - pe.ParentFieldId = NamedId.Of(field.Id, field.Name); + pe.ParentFieldId = field.NamedId(); if (command is FieldCommand fc && @event is FieldEvent fe) { if (field is IArrayField arrayField && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField)) { - fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); + fe.FieldId = nestedField.NamedId(); } } } @@ -357,7 +357,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas { if (@event.SchemaId == null) { - @event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.SchemaDef.Name); + @event.SchemaId = Snapshot.NamedId(); } if (@event.AppId == null) diff --git a/src/Squidex.Domain.Apps.Events/Apps/AppPatternsConfigured.cs b/src/Squidex.Domain.Apps.Events/Apps/AppPatternsConfigured.cs new file mode 100644 index 000000000..4ad20a734 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/Apps/AppPatternsConfigured.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events.Apps +{ + [EventType(nameof(AppPatternsConfigured))] + public sealed class AppPatternsConfigured : AppEvent + { + public AppPattern[] Patterns { get; set; } + } +} diff --git a/src/Squidex.Shared/Permissions.cs b/src/Squidex.Shared/Permissions.cs index 964925efb..4ab584b23 100644 --- a/src/Squidex.Shared/Permissions.cs +++ b/src/Squidex.Shared/Permissions.cs @@ -80,9 +80,7 @@ namespace Squidex.Shared public const string AppPatterns = "squidex.apps.{app}.patterns"; public const string AppPatternsRead = "squidex.apps.{app}.patterns.read"; - public const string AppPatternsCreate = "squidex.apps.{app}.patterns.create"; public const string AppPatternsUpdate = "squidex.apps.{app}.patterns.update"; - public const string AppPatternsDelete = "squidex.apps.{app}.patterns.delete"; public const string AppBackups = "squidex.apps.{app}.backups"; public const string AppBackupsRead = "squidex.apps.{app}.backups.read"; diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 06e050784..0b7d872e5 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -57,7 +57,7 @@ namespace Squidex.Web.CommandMiddlewares throw new InvalidOperationException("Cannot resolve app."); } - return NamedId.Of(appFeature.App.Id, appFeature.App.Name); + return appFeature.App.NamedId(); } } } \ No newline at end of file diff --git a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index 672a16b74..a64798783 100644 --- a/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex.Web/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -69,7 +69,7 @@ namespace Squidex.Web.CommandMiddlewares if (appFeature?.App != null) { - appId = NamedId.Of(appFeature.App.Id, appFeature.App.Name); + appId = appFeature.App.NamedId(); } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index fd4fac1c5..099940efc 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; @@ -55,73 +54,24 @@ namespace Squidex.Areas.Api.Controllers.Apps } /// - /// Create a new app pattern. - /// - /// The name of the app. - /// Pattern to be added to the app. - /// - /// 201 => Pattern generated. - /// 400 => Pattern request not valid. - /// 404 => App not found. - /// - [HttpPost] - [Route("apps/{app}/patterns/")] - [ProducesResponseType(typeof(AppPatternDto), 201)] - [ApiPermission(Permissions.AppPatternsCreate)] - [ApiCosts(1)] - public async Task PostPattern(string app, [FromBody] UpdatePatternDto request) - { - var command = request.ToAddCommand(); - - await CommandBus.PublishAsync(command); - - var response = AppPatternDto.FromCommand(command); - - return CreatedAtAction(nameof(GetPatterns), new { app }, response); - } - - /// - /// Update an existing app pattern. + /// Updates all app pattern. /// /// The name of the app. /// The id of the pattern to be updated. - /// Pattern to be updated for the app. + /// Patterns to be updated for the app. /// - /// 204 => Pattern updated. - /// 400 => Pattern request not valid. - /// 404 => Pattern or app not found. + /// 204 => Patterns updated. + /// 400 => Patterns request not valid. + /// 404 => App not found. /// [HttpPut] - [Route("apps/{app}/patterns/{id}/")] + [Route("apps/{app}/patterns/")] [ProducesResponseType(typeof(AppPatternDto), 201)] [ApiPermission(Permissions.AppPatternsUpdate)] [ApiCosts(1)] - public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) - { - await CommandBus.PublishAsync(request.ToUpdateCommand(id)); - - return NoContent(); - } - - /// - /// Delete an existing app pattern. - /// - /// The name of the app. - /// The id of the pattern to be deleted. - /// - /// 204 => Pattern removed. - /// 404 => Pattern or app not found. - /// - /// - /// Schemas using this pattern will still function using the same Regular Expression. - /// - [HttpDelete] - [Route("apps/{app}/patterns/{id}/")] - [ApiPermission(Permissions.AppPatternsDelete)] - [ApiCosts(1)] - public async Task DeletePattern(string app, Guid id) + public async Task UpdatePatterns(string app, Guid id, [FromBody] ConfigurePatternsDto request) { - await CommandBus.PublishAsync(new DeletePattern { PatternId = id }); + await CommandBus.PublishAsync(request.ToConfigureCommand()); 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 ad8efa50f..a3132e39f 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs @@ -9,18 +9,12 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Apps.Models { public sealed class AppPatternDto { - /// - /// Unique id of the pattern. - /// - public Guid PatternId { get; set; } - /// /// The name of the suggestion. /// @@ -40,12 +34,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models public static AppPatternDto FromKvp(KeyValuePair kvp) { - return SimpleMapper.Map(kvp.Value, new AppPatternDto { PatternId = kvp.Key }); - } - - public static AppPatternDto FromCommand(AddPattern command) - { - return SimpleMapper.Map(command, new AppPatternDto()); + return SimpleMapper.Map(kvp.Value, new AppPatternDto()); } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ConfigurePatternsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ConfigurePatternsDto.cs new file mode 100644 index 000000000..f99f7c38e --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ConfigurePatternsDto.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class ConfigurePatternsDto + { + /// + /// The list of patterns. + /// + [Required] + public AppPatternDto[] Patterns { get; set; } + + public ConfigurePatterns ToConfigureCommand() + { + return new ConfigurePatterns + { + Patterns = Patterns?.Select(p => SimpleMapper.Map(p, new UpsertAppPattern())).ToArray() + }; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs deleted file mode 100644 index 4c225880f..000000000 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdatePatternDto.cs +++ /dev/null @@ -1,44 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Entities.Apps.Commands; -using Squidex.Infrastructure.Reflection; - -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; } - - public AddPattern ToAddCommand() - { - return SimpleMapper.Map(this, new AddPattern()); - } - - public UpdatePattern ToUpdateCommand(Guid id) - { - return SimpleMapper.Map(this, new UpdatePattern { PatternId = id }); - } - } -} diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 1b378c173..923f6a830 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -144,7 +144,7 @@ namespace Squidex.Config.Domain if (!string.IsNullOrWhiteSpace(pattern.Key) && !string.IsNullOrWhiteSpace(pattern.Value)) { - result[Guid.NewGuid()] = new AppPattern(pattern.Key, pattern.Value); + result.Add(new AppPattern(pattern.Key, pattern.Value)); } } 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 56d615159..550c3320b 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternsTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -25,6 +26,31 @@ namespace Squidex.Domain.Apps.Core.Model.Apps patterns_0 = AppPatterns.Empty.Add(firstId, "Default", "Default Pattern", "Message"); } + [Fact] + public void Should_create_patterns() + { + var pattern = new AppPattern("NewPattern", "New Pattern", "Message"); + var patterns = AppPatterns.Create(Enumerable.Repeat(pattern, 1)); + + Assert.Same(pattern, patterns.Values.First()); + } + + [Fact] + public void Should_create_empty_from_null_enumerable() + { + var patterns = AppPatterns.Create(null); + + Assert.Same(AppPatterns.Empty, patterns); + } + + [Fact] + public void Should_create_empty_from_empty_enumerable() + { + var patterns = AppPatterns.Create(Enumerable.Empty()); + + Assert.Same(AppPatterns.Empty, patterns); + } + [Fact] public void Should_add_pattern() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs index 1ac33f4b1..7e7c2933e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppGrainTests.cs @@ -7,8 +7,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using FakeItEasy; +using FluentAssertions; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Services; @@ -37,9 +39,6 @@ namespace Squidex.Domain.Apps.Entities.Apps private readonly string planIdPaid = "premium"; private readonly string planIdFree = "free"; private readonly AppGrain sut; - private readonly Guid patternId1 = Guid.NewGuid(); - private readonly Guid patternId2 = Guid.NewGuid(); - private readonly Guid patternId3 = Guid.NewGuid(); private readonly InitialPatterns initialPatterns; protected override Guid Id @@ -60,8 +59,8 @@ namespace Squidex.Domain.Apps.Entities.Apps initialPatterns = new InitialPatterns { - { patternId1, new AppPattern("Number", "[0-9]") }, - { patternId2, new AppPattern("Numbers", "[0-9]*") } + new AppPattern("Number", "[0-9]"), + new AppPattern("Numbers", "[0-9]*") }; sut = new AppGrain(initialPatterns, Store, A.Dummy(), appPlansProvider, appPlansBillingManager, userResolver); @@ -84,17 +83,16 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 4)); + result.ShouldBeEquivalent(EntityCreatedResult.Create(Id, 3)); Assert.Equal(AppName, sut.Snapshot.Name); LastEvents .ShouldHaveSameEvents( CreateEvent(new AppCreated { Name = AppName }), - CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }), CreateEvent(new AppLanguageAdded { Language = Language.EN }), - CreateEvent(new AppPatternAdded { PatternId = patternId1, Name = "Number", Pattern = "[0-9]" }), - CreateEvent(new AppPatternAdded { PatternId = patternId2, Name = "Numbers", Pattern = "[0-9]*" }) + CreateEvent(new AppContributorAssigned { ContributorId = User.Identifier, Role = Role.Owner }), + CreateEvent(new AppPatternsConfigured { Patterns = initialPatterns.ToArray() }) ); } @@ -103,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid)) .Returns(new PlanChangedResult()); await ExecuteCreateAsync(); @@ -125,10 +123,10 @@ namespace Squidex.Domain.Apps.Entities.Apps { var command = new ChangePlan { PlanId = planIdFree }; - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid)) .Returns(new PlanChangedResult()); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdFree)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdFree)) .Returns(new PlanResetResult()); await ExecuteCreateAsync(); @@ -151,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Apps { var command = new ChangePlan { PlanId = planIdPaid }; - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid)) .Returns(new RedirectToCheckoutResult(new Uri("http://squidex.io"))); await ExecuteCreateAsync(); @@ -172,9 +170,9 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(5)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, planIdPaid)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppNamedId, planIdPaid)) .MustNotHaveHappened(); } @@ -187,7 +185,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 5)); + result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 4)); Assert.Equal(Role.Editor, sut.Snapshot.Contributors[contributorId]); @@ -207,7 +205,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 6)); + result.ShouldBeEquivalent(EntityCreatedResult.Create(contributorId, 5)); Assert.Equal(Role.Owner, sut.Snapshot.Contributors[contributorId]); @@ -227,7 +225,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); Assert.False(sut.Snapshot.Contributors.ContainsKey(contributorId)); @@ -246,7 +244,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(5)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); Assert.True(sut.Snapshot.Clients.ContainsKey(clientId)); @@ -266,7 +264,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); Assert.False(sut.Snapshot.Clients.ContainsKey(clientId)); @@ -286,7 +284,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(7)); + result.ShouldBeEquivalent(new EntitySavedResult(6)); Assert.Equal(clientNewName, sut.Snapshot.Clients[clientId].Name); @@ -306,7 +304,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(5)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); @@ -326,7 +324,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); Assert.False(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); @@ -346,7 +344,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); Assert.True(sut.Snapshot.LanguagesConfig.Contains(Language.DE)); @@ -365,7 +363,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(5)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); Assert.Equal(5, sut.Snapshot.Roles.Count); @@ -385,7 +383,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); Assert.Equal(4, sut.Snapshot.Roles.Count); @@ -405,7 +403,7 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + result.ShouldBeEquivalent(new EntitySavedResult(5)); LastEvents .ShouldHaveSameEvents( @@ -414,59 +412,31 @@ namespace Squidex.Domain.Apps.Entities.Apps } [Fact] - public async Task AddPattern_should_create_events_and_update_state() + public async Task ConfigurePatterns_should_create_events_and_update_state() { - var command = new AddPattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }; - - await ExecuteCreateAsync(); - - var result = await sut.ExecuteAsync(CreateCommand(command)); - - result.ShouldBeEquivalent(new EntitySavedResult(5)); - - Assert.Equal(initialPatterns.Count + 1, sut.Snapshot.Patterns.Count); + var newPatterns = new[] + { + new UpsertAppPattern { Name = "chars", Pattern = "[a-z]*", Message = "Must be a character." } + }; - LastEvents - .ShouldHaveSameEvents( - CreateEvent(new AppPatternAdded { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }) - ); - } + var resultPatterns = new[] + { + new AppPattern("chars", "[a-z]*", "Must be a character.") + }; - [Fact] - public async Task DeletePattern_should_create_events_and_update_state() - { - var command = new DeletePattern { PatternId = patternId3 }; + var command = new ConfigurePatterns { Patterns = newPatterns }; await ExecuteCreateAsync(); - await ExecuteAddPatternAsync(); var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); - - Assert.Equal(initialPatterns.Count, sut.Snapshot.Patterns.Count); - - LastEvents - .ShouldHaveSameEvents( - CreateEvent(new AppPatternDeleted { PatternId = patternId3 }) - ); - } - - [Fact] - public async Task UpdatePattern_should_create_events_and_update_state() - { - var command = new UpdatePattern { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }; - - await ExecuteCreateAsync(); - await ExecuteAddPatternAsync(); - - var result = await sut.ExecuteAsync(CreateCommand(command)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); - result.ShouldBeEquivalent(new EntitySavedResult(6)); + resultPatterns.Should().BeEquivalentTo(sut.Snapshot.Patterns.Values.ToArray()); LastEvents .ShouldHaveSameEvents( - CreateEvent(new AppPatternUpdated { PatternId = patternId3, Name = "Any", Pattern = ".*", Message = "Msg" }) + CreateEvent(new AppPatternsConfigured { Patterns = resultPatterns }) ); } @@ -479,22 +449,17 @@ namespace Squidex.Domain.Apps.Entities.Apps var result = await sut.ExecuteAsync(CreateCommand(command)); - result.ShouldBeEquivalent(new EntitySavedResult(5)); + result.ShouldBeEquivalent(new EntitySavedResult(4)); LastEvents .ShouldHaveSameEvents( CreateEvent(new AppArchived()) ); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppId, AppName, null)) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, AppNamedId, null)) .MustHaveHappened(); } - private Task ExecuteAddPatternAsync() - { - return sut.ExecuteAsync(CreateCommand(new AddPattern { PatternId = patternId3, Name = "Name", Pattern = ".*" })); - } - private Task ExecuteCreateAsync() { return sut.ExecuteAsync(CreateCommand(new CreateApp { Name = AppName })); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs index 39172ada7..547db7299 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Billing/NoopAppPlanBillingManagerTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; using Xunit; @@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Billing [Fact] public async Task Should_do_nothing_when_changing_plan() { - await sut.ChangePlanAsync(null, Guid.Empty, null, null); + await sut.ChangePlanAsync(null, null, null); } [Fact] 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 3bb1902c1..155432071 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs @@ -5,176 +5,135 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Xunit; -#pragma warning disable SA1310 // Field names must not contain underscore - namespace Squidex.Domain.Apps.Entities.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 { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), - new ValidationError("Name is required.", "Name")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_empty() - { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), - new ValidationError("Pattern is required.", "Pattern")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_not_valid() - { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), - new ValidationError("Pattern is not a valid value.", "Pattern")); - } - - [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 { PatternId = patternId, Name = "any", Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command), - new ValidationError("A pattern with the same name already exists.")); - } - - [Fact] - public void CanAdd_should_throw_exception_if_pattern_exists() - { - var patterns_1 = patterns_0.Add(Guid.NewGuid(), "any", "[a-z]", "Message"); - - var command = new AddPattern { PatternId = patternId, Name = "other", Pattern = "[a-z]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_1, command), - new ValidationError("This pattern already exists but with another name.")); - } - [Fact] - public void CanAdd_should_not_throw_exception_if_success() + public void CanConfigure_should_throw_exception_if_two_patterns_with_same_pattern_exist() { - var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = ".*" }; - - GuardAppPatterns.CanAdd(patterns_0, command); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Name = "name1", Pattern = "[a-z]" }, + new UpsertAppPattern { Name = "name2", Pattern = "[a-z]" } + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Two patterns with the same expression exist.", "Patterns")); } [Fact] - public void CanDelete_should_throw_exception_if_pattern_not_found() + public void CanConfigure_should_throw_exception_if_two_patterns_with_same_name_exist() { - var command = new DeletePattern { PatternId = patternId }; - - Assert.Throws(() => GuardAppPatterns.CanDelete(patterns_0, command)); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Name = "name", Pattern = "[a-z]" }, + new UpsertAppPattern { Name = "name", Pattern = "[0-9]" } + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Two patterns with the same name exist.", "Patterns")); } [Fact] - public void CanDelete_should_not_throw_exception_if_success() + public void CanConfigure_should_throw_exception_if_expression_not_valid() { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new DeletePattern { PatternId = patternId }; - - GuardAppPatterns.CanDelete(patterns_1, command); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Name = "name", Pattern = "((" } + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Expression is not a valid value.", "Patterns[1].Pattern")); } [Fact] - public void CanUpdate_should_throw_exception_if_name_empty() + public void CanConfigure_should_throw_exception_if_expression_is_empty() { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = string.Empty, Pattern = ".*" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), - new ValidationError("Name is required.", "Name")); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Name = "name" } + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Expression is required.", "Patterns[1].Pattern")); } [Fact] - public void CanUpdate_should_throw_exception_if_pattern_empty() + public void CanConfigure_should_throw_exception_if_name_is_empty() { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = string.Empty }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), - new ValidationError("Pattern is required.", "Pattern")); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Pattern = "[0-9]" } + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Name is required.", "Patterns[1].Name")); } [Fact] - public void CanUpdate_should_throw_exception_if_pattern_not_valid() + public void CanConfigure_should_throw_exception_if_pattern_is_null() { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), - new ValidationError("Pattern is not a valid value.", "Pattern")); - } - - [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 { PatternId = id2, Name = "Pattern1", Pattern = "[0-4]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command), - new ValidationError("A pattern with the same name already exists.")); + var command = new ConfigurePatterns + { + Patterns = new UpsertAppPattern[] + { + null + } + }; + + ValidationAssert.Throws(() => GuardAppPatterns.CanConfigure(command), + new ValidationError("Pattern is required.", "Patterns[1]")); } [Fact] - public void CanUpdate_should_throw_exception_if_pattern_exists() + public void CanConfigure_should_not_throw_exception_if_patterns_is_valid() { - 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 { PatternId = id2, Name = "Pattern2", Pattern = "[0-5]" }; - - ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_2, command), - new ValidationError("This pattern already exists but with another name.")); + var command = new ConfigurePatterns + { + Patterns = new[] + { + new UpsertAppPattern { Name = "number", Pattern = "[0-9]" } + } + }; + + GuardAppPatterns.CanConfigure(command); } [Fact] - public void CanUpdate_should_throw_exception_if_pattern_does_not_exists() + public void CanConfigure_should_not_throw_exception_if_patterns_is_null() { - var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; + var command = new ConfigurePatterns(); - Assert.Throws(() => GuardAppPatterns.CanUpdate(patterns_0, command)); + GuardAppPatterns.CanConfigure(command); } [Fact] - public void CanUpdate_should_not_throw_exception_if_pattern_exist_with_valid_command() + public void CanConfigure_should_not_throw_exception_if_patterns_is_empty() { - var patterns_1 = patterns_0.Add(patternId, "any", ".*", "Message"); - - var command = new UpdatePattern { PatternId = patternId, Name = "Pattern1", Pattern = ".*" }; + var command = new ConfigurePatterns { Patterns = new UpsertAppPattern[0] }; - GuardAppPatterns.CanUpdate(patterns_1, command); + GuardAppPatterns.CanConfigure(command); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs index 815c45dab..15e5b1cd6 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/History/Notifications/NotificationEmailEventConsumerTests.cs @@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Entities.History.Notifications var @event = new AppContributorAssigned { Actor = new RefToken(assignerType, assignerId), - AppId = new NamedId(Guid.NewGuid(), appName), + AppId = NamedId.Of(Guid.NewGuid(), appName), ContributorId = assigneeId, IsCreated = isNewUser, IsAdded = isNewContributor, diff --git a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs index 16398cc53..191d604fb 100644 --- a/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs +++ b/tests/Squidex.Web.Tests/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs @@ -81,7 +81,7 @@ namespace Squidex.Web.CommandMiddlewares [Fact] public async Task Should_assign_app_id_to_app_self_command() { - var command = new AddPattern(); + var command = new ConfigurePatterns(); var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); @@ -92,7 +92,7 @@ namespace Squidex.Web.CommandMiddlewares [Fact] public async Task Should_not_override_app_id() { - var command = new AddPattern { AppId = Guid.NewGuid() }; + var command = new ConfigurePatterns { AppId = Guid.NewGuid() }; var context = new CommandContext(command, commandBus); await sut.HandleAsync(context); diff --git a/tools/Migrate_01/Migrations/AddPatterns.cs b/tools/Migrate_01/Migrations/AddPatterns.cs index 14dd415ee..7d699fc37 100644 --- a/tools/Migrate_01/Migrations/AddPatterns.cs +++ b/tools/Migrate_01/Migrations/AddPatterns.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; +using System.Linq; using System.Threading.Tasks; using Orleans; using Squidex.Domain.Apps.Entities.Apps; @@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Orleans; +using Squidex.Infrastructure.Reflection; namespace Migrate_01.Migrations { @@ -32,6 +33,11 @@ namespace Migrate_01.Migrations { var ids = await grainFactory.GetGrain(SingleGrain.Id).GetAppIdsAsync(); + var command = new ConfigurePatterns + { + Patterns = initialPatterns.Select(p => SimpleMapper.Map(p, new UpsertAppPattern())).ToArray() + }; + foreach (var id in ids) { var app = grainFactory.GetGrain(id); @@ -40,21 +46,10 @@ namespace Migrate_01.Migrations if (state.Value.Patterns.Count == 0) { - foreach (var pattern in initialPatterns.Values) - { - var command = - new AddPattern - { - Actor = state.Value.CreatedBy, - AppId = state.Value.Id, - Name = pattern.Name, - PatternId = Guid.NewGuid(), - Pattern = pattern.Pattern, - Message = pattern.Message - }; - - await app.ExecuteAsync(command); - } + command.AppId = state.Value.Id; + command.Actor = state.Value.CreatedBy; + + await app.ExecuteAsync(command); } } }