From 2b0681ef6791cef78a3e61c11b521cec833b95a6 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 22 Oct 2017 16:11:18 +0200 Subject: [PATCH] Json support --- Squidex.sln.DotSettings | 1 + .../Apps/AppClient.cs | 20 ++- .../Apps/AppClients.cs | 16 +- .../Apps/AppContributors.cs | 14 +- .../Apps/Json/AppClientsConverter.cs | 50 +++++++ .../Apps/Json/AppContributorsConverter.cs | 50 +++++++ .../Apps/Json/JsonAppClient.cs | 39 +++++ .../Apps/Json/JsonLanguageConfig.cs | 40 +++++ .../Apps/Json/LanguagesConfigConverter.cs | 52 +++++++ .../{ => Apps}/LanguageConfig.cs | 2 +- .../{ => Apps}/LanguagesConfig.cs | 36 +++-- .../DictionaryBase.cs | 63 ++++++++ .../Schemas/FieldProperties.cs | 2 - .../Schemas/NamedElementPropertiesBase.cs | 2 - .../ConvertContent/ContentConverter.cs | 1 + .../EnrichContent/ContentEnricher.cs | 1 - .../EnrichContent/DefaultValueFactory.cs | 1 - .../Apps/Utils/AppEventDispatcher.cs | 5 +- .../Apps/MongoAppEntity.cs | 6 +- .../Apps/MongoAppRepository.cs | 5 +- .../Apps/MongoAppRepository_EventHandling.cs | 2 +- .../Contents/Extensions.cs | 16 +- .../Contents/MongoContentEntity.cs | 14 +- .../Contents/MongoContentRepository.cs | 12 +- .../MongoContentRepository_EventHandling.cs | 6 +- .../Schemas/MongoSchemaEntity.cs | 2 +- .../Schemas/MongoSchemaRepository.cs | 6 +- .../Apps/IAppClientEntity.cs | 21 --- .../Apps/AppCommandMiddleware.cs | 29 +++- .../Apps/AppDomainObject.cs | 1 - .../Apps/Commands/ChangePlan.cs | 2 - .../Apps/Guards/GuardAppClients.cs | 102 +++++++++++++ .../Apps/Guards/GuardAppContributors.cs | 8 +- .../Apps/Guards/GuardAppLanguages.cs | 14 +- .../Contents/Commands/PatchContent.cs | 2 - .../Contents/Commands/UpdateContent.cs | 2 - .../Contents/ContentCommandMiddleware.cs | 111 +++++--------- .../Contents/ContentOperationContext.cs | 134 +++++++++++++++++ .../Contents/Guards/GuardContent.cs | 11 +- .../Schemas/Guards/GuardSchema.cs | 1 - .../UserClaimsPrincipalFactoryWithEmail.cs | 1 - .../MongoDb/BsonJsonAttribute.cs} | 9 +- .../MongoDb/BsonJsonConvention.cs | 35 +++++ .../CollectionExtensions.cs | 16 ++ .../DictionaryWrapper.cs | 84 ----------- src/Squidex/Config/Domain/Serializers.cs | 7 + .../Config/Identity/LazyClientStore.cs | 4 +- .../Api/Apps/AppContributorsController.cs | 2 +- .../Api/Apps/AppLanguagesController.cs | 2 +- .../Controllers/Api/Apps/AppsController.cs | 2 +- .../Models/NumberFieldPropertiesDto.cs | 6 - .../Models/StringFieldPropertiesDto.cs | 6 - .../ContentApi/ContentsController.cs | 1 + .../Generator/SchemaSwaggerGenerator.cs | 2 +- .../Pipeline/AppPermissionAttribute.cs | 4 +- src/Squidex/Squidex.csproj | 4 +- .../Model/Apps/AppClientsTests.cs | 103 +++++++++++++ .../Model/Apps/AppContributorsTests.cs | 70 +++++++++ .../Model/Apps/AppPlanTests.cs | 32 ++++ .../Model/Apps/LanguagesConfigJsonTests.cs | 35 +++++ .../Model/{ => Apps}/LanguagesConfigTests.cs | 3 +- .../Model/Contents/ContentDataTests.cs | 17 +++ .../Model/Schemas/Json/JsonSerializerTests.cs | 55 ------- .../Model/Schemas/SchemaFieldTests.cs | 91 ++++++++++++ .../Model/Schemas/SchemaTests.cs | 83 ++--------- .../ConvertContent/ContentConversionTests.cs | 3 +- .../EnrichContent/ContentEnrichmentTests.cs | 1 + .../ReferenceExtractionTests.cs | 1 + .../Operations/GenerateEdmSchema/EdmTests.cs | 1 + .../GenerateJsonSchema/JsonSchemaTests.cs | 1 + .../ValidateContent/ContentValidationTests.cs | 1 + .../ValidateContent/NumberFieldTests.cs | 1 - .../ValidateContent/StringFieldTests.cs | 1 - .../TestData.cs | 33 +++++ .../Contents/GraphQLTests.cs | 50 ++++--- .../Contents/ODataQueryTests.cs | 49 +++--- .../Apps/Guards/GuardAppClientsTests.cs | 140 ++++++++++++++++++ .../Apps/Guards/GuardAppContributorsTests.cs | 25 +--- .../Apps/Guards/GuardAppLanguagesTests.cs | 38 ++--- .../Contents/ContentCommandMiddlewareTests.cs | 1 + .../Contents/Guard/GuardContentTests.cs | 99 +++++++++++++ 81 files changed, 1488 insertions(+), 533 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppClient.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs create mode 100644 src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs rename src/Squidex.Domain.Apps.Core.Model/{ => Apps}/LanguageConfig.cs (97%) rename src/Squidex.Domain.Apps.Core.Model/{ => Apps}/LanguagesConfig.cs (90%) create mode 100644 src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs create mode 100644 src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs create mode 100644 src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs rename src/{Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs => Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs} (62%) create mode 100644 src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs delete mode 100644 src/Squidex.Infrastructure/DictionaryWrapper.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs rename tests/Squidex.Domain.Apps.Core.Tests/Model/{ => Apps}/LanguagesConfigTests.cs (98%) delete mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs diff --git a/Squidex.sln.DotSettings b/Squidex.sln.DotSettings index cf27c2d87..0e0d4202d 100644 --- a/Squidex.sln.DotSettings +++ b/Squidex.sln.DotSettings @@ -17,6 +17,7 @@ + <?xml version="1.0" encoding="utf-16"?><Profile name="Header"><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> <?xml version="1.0" encoding="utf-16"?><Profile name="Namespaces"><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> <?xml version="1.0" encoding="utf-16"?><Profile name="Typescript"><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs></Profile> diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs index fe4fa0bc5..28e17d013 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs @@ -16,14 +16,30 @@ namespace Squidex.Domain.Apps.Core.Apps private string name; private AppClientPermission permission; - public AppClient(string name, string secret) + public string Name + { + get { return name; } + } + + public string Secret + { + get { return secret; } + } + + public AppClientPermission Permission + { + get { return permission; } + } + + public AppClient(string name, string secret, AppClientPermission permission) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(secret, nameof(secret)); + Guard.Enum(permission, nameof(permission)); this.name = name; - this.secret = secret; + this.permission = permission; } public void Update(AppClientPermission newPermission) diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs index 07001417f..a92de925b 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs @@ -6,32 +6,32 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Apps { - public class AppClients + public sealed class AppClients : DictionaryBase { - private readonly Dictionary clients = new Dictionary(); - - public IReadOnlyDictionary Clients + public void Add(string id, AppClient client) { - get { return clients; } + Guard.NotNullOrEmpty(id, nameof(id)); + Guard.NotNull(client, nameof(client)); + + Inner.Add(id, client); } public void Add(string id, string secret) { Guard.NotNullOrEmpty(id, nameof(id)); - clients.Add(id, new AppClient(secret, id)); + Inner.Add(id, new AppClient(id, secret, AppClientPermission.Editor)); } public void Revoke(string id) { Guard.NotNullOrEmpty(id, nameof(id)); - clients.Remove(id); + Inner.Remove(id); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs index 780ed8076..9c4ce924d 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs @@ -6,33 +6,25 @@ // All rights reserved. // ========================================================================== -using System.Collections.Generic; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Apps { - public class AppContributors + public sealed class AppContributors : DictionaryBase { - private readonly Dictionary contributors = new Dictionary(); - - public IReadOnlyDictionary Contributors - { - get { return contributors; } - } - public void Assign(string contributorId, AppContributorPermission permission) { Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.Enum(permission, nameof(permission)); - contributors[contributorId] = permission; + Inner[contributorId] = permission; } public void Remove(string contributorId) { Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); - contributors.Remove(contributorId); + Inner.Remove(contributorId); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs new file mode 100644 index 000000000..908f78c08 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// AppClientsConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public sealed class AppClientsConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var clients = (AppClients)value; + + var json = new Dictionary(clients.Count); + + foreach (var client in clients) + { + json.Add(client.Key, new JsonAppClient(client.Value)); + } + + serializer.Serialize(writer, json); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var json = serializer.Deserialize>(reader); + + var clients = new AppClients(); + + foreach (var client in json) + { + clients.Add(client.Key, client.Value.ToClient()); + } + + return clients; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AppClients); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs new file mode 100644 index 000000000..98c45ad6e --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// AppContributorsConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public sealed class AppContributorsConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var contributors = (AppContributors)value; + + var json = new Dictionary(contributors.Count); + + foreach (var contributor in contributors) + { + json.Add(contributor.Key, contributor.Value); + } + + serializer.Serialize(writer, json); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var json = serializer.Deserialize>(reader); + + var contributors = new AppContributors(); + + foreach (var contributor in json) + { + contributors.Assign(contributor.Key, contributor.Value); + } + + return contributors; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AppContributors); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppClient.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppClient.cs new file mode 100644 index 000000000..c3b11189e --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppClient.cs @@ -0,0 +1,39 @@ +// ========================================================================== +// JsonAppClient.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Newtonsoft.Json; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public class JsonAppClient + { + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public string Secret { get; set; } + + [JsonProperty] + public AppClientPermission Permission { get; set; } + + public JsonAppClient() + { + } + + public JsonAppClient(AppClient client) + { + SimpleMapper.Map(client, this); + } + + public AppClient ToClient() + { + return new AppClient(Name, Secret, Permission); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs new file mode 100644 index 000000000..f85cb70d3 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// JsonLanguageConfig.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using Newtonsoft.Json; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public class JsonLanguageConfig + { + [JsonProperty] + public Language[] Fallback { get; set; } + + [JsonProperty] + public bool IsOptional { get; set; } + + public JsonLanguageConfig() + { + } + + public JsonLanguageConfig(LanguageConfig config) + { + SimpleMapper.Map(config, this); + + Fallback = config.LanguageFallbacks.ToArray(); + } + + public LanguageConfig ToConfig(string language) + { + return new LanguageConfig(language, IsOptional, Fallback); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs new file mode 100644 index 000000000..d1f5d6485 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// AppClientsConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Squidex.Domain.Apps.Core.Apps.Json +{ + public sealed class LanguagesConfigConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var languagesConfig = (LanguagesConfig)value; + + var json = new Dictionary(languagesConfig.Count); + + foreach (var config in languagesConfig.Configs) + { + json.Add(config.Language, new JsonLanguageConfig(config)); + } + + serializer.Serialize(writer, json); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var json = serializer.Deserialize>(reader); + + var languagesConfig = new LanguageConfig[json.Count]; + + var i = 0; + + foreach (var config in json) + { + languagesConfig[i++] = config.Value.ToConfig(config.Key); + } + + return LanguagesConfig.Build(languagesConfig); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(LanguagesConfig); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/LanguageConfig.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs similarity index 97% rename from src/Squidex.Domain.Apps.Core.Model/LanguageConfig.cs rename to src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs index 63fd8904f..b1d507067 100644 --- a/src/Squidex.Domain.Apps.Core.Model/LanguageConfig.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs @@ -10,7 +10,7 @@ using System.Collections.Generic; using System.Linq; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core +namespace Squidex.Domain.Apps.Core.Apps { public sealed class LanguageConfig : IFieldPartitionItem { diff --git a/src/Squidex.Domain.Apps.Core.Model/LanguagesConfig.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs similarity index 90% rename from src/Squidex.Domain.Apps.Core.Model/LanguagesConfig.cs rename to src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs index c1d8fb2c8..8f0c6f1f9 100644 --- a/src/Squidex.Domain.Apps.Core.Model/LanguagesConfig.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs @@ -13,7 +13,7 @@ using System.Collections.Immutable; using System.Linq; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Core +namespace Squidex.Domain.Apps.Core.Apps { public sealed class LanguagesConfig : IFieldPartitioning { @@ -24,11 +24,6 @@ namespace Squidex.Domain.Apps.Core get { return state.Master; } } - public int Count - { - get { return state.Languages.Count; } - } - IFieldPartitionItem IFieldPartitioning.Master { get { return state.Master; } @@ -44,6 +39,16 @@ namespace Squidex.Domain.Apps.Core return state.Languages.Values.GetEnumerator(); } + public IEnumerable Configs + { + get { return state.Languages.Values; } + } + + public int Count + { + get { return state.Languages.Count; } + } + private LanguagesConfig(ICollection configs) { Guard.NotNull(configs, nameof(configs)); @@ -83,7 +88,7 @@ namespace Squidex.Domain.Apps.Core { Guard.NotNull(language, nameof(language)); - state = new State( + var newLanguages = state.Languages.Values.Where(x => x.Language != language) .Select(config => { @@ -92,7 +97,14 @@ namespace Squidex.Domain.Apps.Core config.IsOptional, config.LanguageFallbacks.Except(new[] { language })); }) - .ToImmutableDictionary(x => x.Language), state.Master.Language == language ? null : state.Master); + .ToImmutableDictionary(x => x.Language); + + var newMaster = + state.Master.Language != language ? + state.Master : + null; + + state = new State(newLanguages, newMaster); } public bool Contains(Language language) @@ -107,16 +119,18 @@ namespace Squidex.Domain.Apps.Core public bool TryGetItem(string key, out IFieldPartitionItem item) { - item = null; - if (Language.IsValidLanguage(key) && state.Languages.TryGetValue(key, out var value)) { item = value; return true; } + else + { + item = null; - return false; + return false; + } } private sealed class State diff --git a/src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs b/src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs new file mode 100644 index 000000000..6b3734c6a --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs @@ -0,0 +1,63 @@ +// ========================================================================== +// DictionaryBase.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections; +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Core +{ + public abstract class DictionaryBase : IReadOnlyDictionary + { + private readonly Dictionary inner = new Dictionary(); + + public TValue this[TKey key] + { + get { return inner[key]; } + } + + public IEnumerable Keys + { + get { return inner.Keys; } + } + + public IEnumerable Values + { + get { return inner.Values; } + } + + public int Count + { + get { return inner.Count; } + } + + protected Dictionary Inner + { + get { return inner; } + } + + public bool ContainsKey(TKey key) + { + return inner.ContainsKey(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return inner.TryGetValue(key, out value); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs index 334d5b59e..c975e4ef9 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using Newtonsoft.Json.Linq; - namespace Squidex.Domain.Apps.Core.Schemas { public abstract class FieldProperties : NamedElementPropertiesBase diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs index dfcbba099..7f5a9d572 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using System; - namespace Squidex.Domain.Apps.Core.Schemas { public abstract class NamedElementPropertiesBase diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index 30edad325..87c986cb9 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs index 10f73cb5e..0aabe935c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Contents; diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs index d7529af47..0cbd275e2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; diff --git a/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs index 7e4442977..54f09a5ef 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs +++ b/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; namespace Squidex.Domain.Apps.Events.Apps.Utils @@ -45,7 +44,7 @@ namespace Squidex.Domain.Apps.Events.Apps.Utils public static void Apply(this AppClients clients, AppClientRenamed @event) { - if (clients.Clients.TryGetValue(@event.Id, out var client)) + if (clients.TryGetValue(@event.Id, out var client)) { client.Rename(@event.Name); } @@ -53,7 +52,7 @@ namespace Squidex.Domain.Apps.Events.Apps.Utils public static void Apply(this AppClients clients, AppClientUpdated @event) { - if (clients.Clients.TryGetValue(@event.Id, out var client)) + if (clients.TryGetValue(@event.Id, out var client)) { client.Update(@event.Permission); } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs index 7810f2814..b7799d63a 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs @@ -39,17 +39,17 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps [BsonRequired] [BsonElement] - [BsonSerializer(typeof(JsonBsonSerializer))] + [BsonJson] public AppClients Clients { get; set; } = new AppClients(); [BsonRequired] [BsonElement] - [BsonSerializer(typeof(JsonBsonSerializer))] + [BsonJson] public AppContributors Contributors { get; set; } = new AppContributors(); [BsonRequired] [BsonElement] - [BsonSerializer(typeof(JsonBsonSerializer))] + [BsonJson] public LanguagesConfig LanguagesConfig { get; } = LanguagesConfig.Build(Language.EN); public PartitionResolver PartitionResolver diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs index aabfa2ebd..c6f475e2e 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs @@ -10,9 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MongoDB.Bson.Serialization; using MongoDB.Driver; -using Newtonsoft.Json; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Repositories; using Squidex.Infrastructure.CQRS.Events; @@ -22,10 +20,9 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps { public partial class MongoAppRepository : MongoRepositoryBase, IAppRepository, IEventConsumer { - public MongoAppRepository(IMongoDatabase database, JsonSerializer serializer) + public MongoAppRepository(IMongoDatabase database) : base(database) { - BsonSerializer.RegisterSerializer(new JsonBsonSerializer(serializer)); } protected override string CollectionName() diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index d4d22106b..d70f8aebf 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -130,7 +130,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Apps { updater(a); - a.ContributorIds = a.Contributors.Contributors.Keys.ToArray(); + a.ContributorIds = a.Contributors.Keys.ToArray(); }); } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs index bd8e91547..be7dcb916 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs @@ -10,14 +10,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using MongoDB.Bson; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Read.MongoDb.Contents { @@ -25,23 +22,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { private const int MaxLength = 1024 * 1024; - public static BsonDocument ToBsonDocument(this IdContentData data, JsonSerializer jsonSerializer) - { - return (BsonDocument)JToken.FromObject(data, jsonSerializer).ToBson(); - } - public static List ToReferencedIds(this IdContentData data, Schema schema) { return data.GetReferencedIds(schema).ToList(); } - public static NamedContentData ToData(this BsonDocument document, Schema schema, List deletedIds, JsonSerializer jsonSerializer) + public static NamedContentData ToData(this IdContentData idData, Schema schema, List deletedIds) { - return document - .ToJson() - .ToObject(jsonSerializer) - .ToCleanedReferences(schema, new HashSet(deletedIds ?? new List())) - .ToNameModel(schema, true); + return idData.ToCleanedReferences(schema, new HashSet(deletedIds)).ToNameModel(schema, true); } public static string ToFullText(this ContentData data) diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs index 5ca934612..141e33e6f 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs @@ -10,7 +10,6 @@ using System; using System.Collections.Generic; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; @@ -66,10 +65,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents [BsonElement("mb")] public RefToken LastModifiedBy { get; set; } - [BsonRequired] - [BsonElement("do")] - public BsonDocument DataDocument { get; set; } - [BsonRequired] [BsonElement("rf")] public List ReferencedIds { get; set; } @@ -78,14 +73,19 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents [BsonElement("rd")] public List ReferencedIdsDeleted { get; set; } = new List(); + [BsonRequired] + [BsonElement("do")] + [BsonJson] + public IdContentData IdData { get; set; } + NamedContentData IContentEntity.Data { get { return data; } } - public void ParseData(Schema schema, JsonSerializer serializer) + public void ParseData(Schema schema) { - data = DataDocument.ToData(schema, ReferencedIdsDeleted, serializer); + data = IdData.ToData(schema, ReferencedIdsDeleted); } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs index 4f124278d..5ca896110 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs @@ -13,7 +13,6 @@ using System.Threading.Tasks; using Microsoft.OData.UriParser; using MongoDB.Bson; using MongoDB.Driver; -using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Contents; @@ -31,7 +30,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents private const string Prefix = "Projections_Content_"; private readonly IMongoDatabase database; private readonly ISchemaProvider schemas; - private readonly JsonSerializer serializer; protected static FilterDefinitionBuilder Filter { @@ -65,15 +63,13 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents } } - public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas, JsonSerializer serializer) + public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas) { Guard.NotNull(database, nameof(database)); Guard.NotNull(schemas, nameof(schemas)); - Guard.NotNull(serializer, nameof(serializer)); this.database = database; this.schemas = schemas; - this.serializer = serializer; } public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) @@ -103,7 +99,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents foreach (var entity in contentEntities) { - entity.ParseData(schema.SchemaDef, serializer); + entity.ParseData(schema.SchemaDef); } return contentEntities; @@ -151,7 +147,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents foreach (var entity in contentEntities) { - entity.ParseData(schema.SchemaDef, serializer); + entity.ParseData(schema.SchemaDef); } return contentEntities.OfType().ToList(); @@ -176,7 +172,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents await collection.Find(x => x.Id == id) .FirstOrDefaultAsync(); - contentEntity?.ParseData(schema.SchemaDef, serializer); + contentEntity?.ParseData(schema.SchemaDef); return contentEntity; } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs index 61900dd4f..b76240365 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); content.DataText = idData?.ToFullText(); - content.DataDocument = idData?.ToBsonDocument(serializer); + content.IdData = idData; content.ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef); }); }); @@ -90,13 +90,13 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents { return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) => { - var idData = @event.Data.ToIdModel(schema.SchemaDef, true); + var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); return collection.UpdateOneAsync( Filter.Eq(x => x.Id, @event.ContentId), Update .Set(x => x.DataText, idData.ToFullText()) - .Set(x => x.DataDocument, idData.ToBsonDocument(serializer)) + .Set(x => x.IdData, idData) .Set(x => x.ReferencedIds, idData.ToReferencedIds(schema.SchemaDef)) .Set(x => x.LastModified, headers.Timestamp()) .Set(x => x.LastModifiedBy, @event.Actor) diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs index bce8e652d..7efc0059b 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Schemas [BsonRequired] [BsonElement] - [BsonSerializer(typeof(JsonBsonSerializer))] + [BsonJson] public Schema SchemaDef { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs index a4d8ac949..a43da664f 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs @@ -10,9 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MongoDB.Bson.Serialization; using MongoDB.Driver; -using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas.Repositories; @@ -26,14 +24,12 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Schemas { private readonly FieldRegistry registry; - public MongoSchemaRepository(IMongoDatabase database, JsonSerializer serializer, FieldRegistry registry) + public MongoSchemaRepository(IMongoDatabase database, FieldRegistry registry) : base(database) { Guard.NotNull(registry, nameof(registry)); this.registry = registry; - - BsonSerializer.RegisterSerializer(new JsonBsonSerializer(serializer)); } protected override string CollectionName() diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs b/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs deleted file mode 100644 index 73cae6fba..000000000 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// IAppClientEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Apps; - -namespace Squidex.Domain.Apps.Read.Apps -{ - public interface IAppClientEntity - { - string Name { get; } - - string Secret { get; } - - AppClientPermission Permission { get; } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs index 327761d38..03f5957bd 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs @@ -58,11 +58,6 @@ namespace Squidex.Domain.Apps.Write.Apps }); } - protected Task On(AttachClient command, CommandContext context) - { - return handler.UpdateAsync(context, a => a.AttachClient(command)); - } - protected async Task On(AssignContributor command, CommandContext context) { await handler.UpdateAsync(context, async a => @@ -83,14 +78,34 @@ namespace Squidex.Domain.Apps.Write.Apps }); } + protected Task On(AttachClient command, CommandContext context) + { + return handler.UpdateAsync(context, a => + { + GuardAppClients.CanAttach(a.Clients, command); + + a.AttachClient(command); + }); + } + protected Task On(UpdateClient command, CommandContext context) { - return handler.UpdateAsync(context, a => a.UpdateClient(command)); + return handler.UpdateAsync(context, a => + { + GuardAppClients.CanUpdate(a.Clients, command); + + a.UpdateClient(command); + }); } protected Task On(RevokeClient command, CommandContext context) { - return handler.UpdateAsync(context, a => a.RevokeClient(command)); + return handler.UpdateAsync(context, a => + { + GuardAppClients.CanRevoke(a.Clients, command); + + a.RevokeClient(command); + }); } protected Task On(AddLanguage command, CommandContext context) diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs index ac265d97e..d891c619a 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs @@ -7,7 +7,6 @@ // ========================================================================== using System; -using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs b/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs index a15d68fb9..4d84ee36c 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using Squidex.Infrastructure; - namespace Squidex.Domain.Apps.Write.Apps.Commands { public sealed class ChangePlan : AppAggregateCommand diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs new file mode 100644 index 000000000..3a19fe5ea --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs @@ -0,0 +1,102 @@ +// ========================================================================== +// GuardAppClients.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Write.Apps.Guards +{ + public static class GuardAppClients + { + public static void CanAttach(AppClients clients, AttachClient command) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot attach client.", error => + { + if (string.IsNullOrWhiteSpace(command.Id)) + { + error(new ValidationError("Client id must be defined.", nameof(command.Id))); + } + else if (clients.ContainsKey(command.Id)) + { + error(new ValidationError("Client id already added.", nameof(command.Id))); + } + }); + } + + public static void CanRevoke(AppClients clients, RevokeClient command) + { + Guard.NotNull(command, nameof(command)); + + GetClientOrThrow(clients, command.Id); + + Validate.It(() => "Cannot revoke client.", error => + { + if (string.IsNullOrWhiteSpace(command.Id)) + { + error(new ValidationError("Client id must be defined.", nameof(command.Id))); + } + }); + } + + public static void CanUpdate(AppClients clients, UpdateClient command) + { + Guard.NotNull(command, nameof(command)); + + var client = GetClientOrThrow(clients, command.Id); + + Validate.It(() => "Cannot revoke client.", error => + { + if (string.IsNullOrWhiteSpace(command.Id)) + { + error(new ValidationError("Client id must be defined.", nameof(command.Id))); + } + + if (string.IsNullOrWhiteSpace(command.Name) && command.Permission == null) + { + error(new ValidationError("Either name or permission must be defined.", nameof(command.Name), nameof(command.Permission))); + } + + if (command.Permission.HasValue && !command.Permission.Value.IsEnumValue()) + { + error(new ValidationError("Permission is not valid.", nameof(command.Permission))); + } + + if (client != null) + { + if (!string.IsNullOrWhiteSpace(command.Name) && string.Equals(client.Name, command.Name)) + { + error(new ValidationError("Client already has this name.", nameof(command.Permission))); + } + + if (command.Permission == client.Permission) + { + error(new ValidationError("Client already has this permission.", nameof(command.Permission))); + } + } + }); + } + + private static AppClient GetClientOrThrow(AppClients clients, string id) + { + if (id == null) + { + return null; + } + + if (!clients.TryGetValue(id, out var client)) + { + throw new DomainObjectNotFoundException(id, "Clients", typeof(AppDomainObject)); + } + + return client; + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs index daee2032b..64ecb94f1 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs @@ -39,14 +39,14 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { error(new ValidationError("Cannot find contributor id.", nameof(command.ContributorId))); } - else if (contributors.Contributors.TryGetValue(command.ContributorId, out var existing)) + else if (contributors.TryGetValue(command.ContributorId, out var existing)) { if (existing == command.Permission) { error(new ValidationError("Contributor has already this permission.", nameof(command.Permission))); } } - else if (plan.MaxContributors == contributors.Contributors.Count) + else if (plan.MaxContributors == contributors.Count) { error(new ValidationError("You have reached the maximum number of contributors for your plan.")); } @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards error(new ValidationError("Contributor id not assigned.", nameof(command.ContributorId))); } - var ownerIds = contributors.Contributors.Where(x => x.Value == AppContributorPermission.Owner).Select(x => x.Key).ToList(); + var ownerIds = contributors.Where(x => x.Value == AppContributorPermission.Owner).Select(x => x.Key).ToList(); if (ownerIds.Count == 1 && ownerIds.Contains(command.ContributorId)) { @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards } }); - if (!contributors.Contributors.ContainsKey(command.ContributorId)) + if (!contributors.ContainsKey(command.ContributorId)) { throw new DomainObjectNotFoundException(command.ContributorId, "Contributors", typeof(AppDomainObject)); } diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs index a65e5d05b..1ac39c4b2 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs +++ b/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; @@ -39,6 +39,11 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards Validate.It(() => "Cannot remove language.", error => { + if (command.Language == null) + { + error(new ValidationError("Language cannot be null.", nameof(command.Language))); + } + if (languages.Master == languageConfig) { error(new ValidationError("Language config is master.", nameof(command.Language))); @@ -54,6 +59,11 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards Validate.It(() => "Cannot update language.", error => { + if (command.Language == null) + { + error(new ValidationError("Language cannot be null.", nameof(command.Language))); + } + if ((languages.Master == languageConfig || command.IsMaster) && command.IsOptional) { error(new ValidationError("Cannot make master language optional.", nameof(command.IsMaster))); @@ -76,7 +86,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { if (language == null) { - throw new DomainObjectNotFoundException(language, "Languages", typeof(AppDomainObject)); + return null; } if (!languages.TryGetConfig(language, out var languageConfig)) diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs index 410315879..ff38a5638 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Core.Contents; - namespace Squidex.Domain.Apps.Write.Contents.Commands { public sealed class PatchContent : ContentDataCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs index 47d6128ad..be9546173 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs @@ -6,8 +6,6 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Core.Contents; - namespace Squidex.Domain.Apps.Write.Contents.Commands { public sealed class UpdateContent : ContentDataCommand diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs index 810eb7b8e..d64143b2f 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs @@ -7,19 +7,14 @@ // ========================================================================== using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Write.Contents.Guards; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Dispatching; @@ -62,18 +57,18 @@ namespace Squidex.Domain.Apps.Write.Contents { await handler.CreateAsync(context, async content => { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); + GuardContent.CanCreate(command); - ExecuteScriptAndTransform(command, content, schemaAndApp.SchemaEntity.ScriptCreate, "Create"); + var operationContext = await CreateContext(command, content, () => "Failed to create content."); if (command.Publish) { - ExecuteScript(command, content, schemaAndApp.SchemaEntity.ScriptChange, "Published"); + await operationContext.ExecuteScriptAsync(x => x.ScriptChange, "Published"); } - command.Data.Enrich(schemaAndApp.SchemaEntity.SchemaDef, schemaAndApp.AppEntity.PartitionResolver); - - await ValidateAsync(schemaAndApp, command, () => "Failed to create content", false); + await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptCreate, "Create"); + await operationContext.EnrichAsync(); + await operationContext.ValidateAsync(false); content.Create(command); @@ -85,11 +80,12 @@ namespace Squidex.Domain.Apps.Write.Contents { await handler.UpdateAsync(context, async content => { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); + GuardContent.CanUpdate(command); - ExecuteScriptAndTransform(command, content, schemaAndApp.SchemaEntity.ScriptUpdate, "Update"); + var operationContext = await CreateContext(command, content, () => "Failed to update content."); - await ValidateAsync(schemaAndApp, command, () => "Failed to update content", false); + await operationContext.ValidateAsync(true); + await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Update"); content.Update(command); @@ -101,11 +97,12 @@ namespace Squidex.Domain.Apps.Write.Contents { await handler.UpdateAsync(context, async content => { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); + GuardContent.CanPatch(command); - ExecuteScriptAndTransform(command, content, schemaAndApp.SchemaEntity.ScriptUpdate, "Patch"); + var operationContext = await CreateContext(command, content, () => "Failed to patch content."); - await ValidateAsync(schemaAndApp, command, () => "Failed to patch content", true); + await operationContext.ValidateAsync(true); + await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Patch"); content.Patch(command); @@ -117,9 +114,11 @@ namespace Squidex.Domain.Apps.Write.Contents { return handler.UpdateAsync(context, async content => { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); + GuardContent.CanChangeContentStatus(content.Status, command); + + var operationContext = await CreateContext(command, content, () => "Failed to patch content."); - ExecuteScript(command, content, schemaAndApp.SchemaEntity.ScriptChange, command.Status); + await operationContext.ExecuteScriptAsync(x => x.ScriptChange, command.Status); content.ChangeStatus(command); }); @@ -129,9 +128,11 @@ namespace Squidex.Domain.Apps.Write.Contents { return handler.UpdateAsync(context, async content => { - var schemaAndApp = await ResolveSchemaAndAppAsync(command); + GuardContent.CanDelete(command); + + var operationContext = await CreateContext(command, content, () => "Failed to delete content."); - ExecuteScript(command, content, schemaAndApp.SchemaEntity.ScriptDelete, "Delete"); + await operationContext.ExecuteScriptAsync(x => x.ScriptDelete, "Delete"); content.Delete(command); }); @@ -145,60 +146,20 @@ namespace Squidex.Domain.Apps.Write.Contents } } - private async Task ValidateAsync((ISchemaEntity Schema, IAppEntity App) schemaAndApp, ContentDataCommand command, Func message, bool partial) + private async Task CreateContext(ContentCommand command, ContentDomainObject content, Func message) { - var schemaErrors = new List(); - - var appId = command.AppId.Id; - - var validationContext = - new ValidationContext( - (contentIds, schemaId) => - { - return contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); - }, - assetIds => - { - return assetRepository.QueryNotFoundAsync(appId, assetIds.ToList()); - }); - - if (partial) - { - await command.Data.ValidatePartialAsync(validationContext, schemaAndApp.Schema.SchemaDef, schemaAndApp.App.PartitionResolver, schemaErrors); - } - else - { - await command.Data.ValidateAsync(validationContext, schemaAndApp.Schema.SchemaDef, schemaAndApp.App.PartitionResolver, schemaErrors); - } - - if (schemaErrors.Count > 0) - { - throw new ValidationException(message(), schemaErrors); - } - } - - private void ExecuteScriptAndTransform(ContentDataCommand command, ContentDomainObject content, string script, object operation) - { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString(), Data = command.Data }; - - command.Data = scriptEngine.ExecuteAndTransform(ctx, script); - } - - private void ExecuteScript(ContentCommand command, ContentDomainObject content, string script, object operation) - { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString() }; - - scriptEngine.Execute(ctx, script); - } - - private async Task<(ISchemaEntity SchemaEntity, IAppEntity AppEntity)> ResolveSchemaAndAppAsync(SchemaCommand command) - { - var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id); - var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id); - - await Task.WhenAll(taskForApp, taskForSchema); - - return (taskForSchema.Result, taskForApp.Result); + var operationContext = + await ContentOperationContext.CreateAsync( + contentRepository, + content, + command, + appProvider, + schemas, + scriptEngine, + assetRepository, + message); + + return operationContext; } } } diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs new file mode 100644 index 000000000..72a4ad097 --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs @@ -0,0 +1,134 @@ +// ========================================================================== +// ContentOperationContext.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.EnrichContent; +using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Read.Contents.Repositories; +using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Domain.Apps.Write.Contents +{ + public sealed class ContentOperationContext + { + private ContentDomainObject content; + private ContentCommand command; + private IContentRepository contentRepository; + private IAssetRepository assetRepository; + private IScriptEngine scriptEngine; + private ISchemaEntity schemaEntity; + private IAppEntity appEntity; + private Func message; + + public static async Task CreateAsync( + IContentRepository contentRepository, + ContentDomainObject content, + ContentCommand command, + IAppProvider appProvider, + ISchemaProvider schemas, + IScriptEngine scriptEngine, + IAssetRepository assetRepository, + Func message) + { + var taskForApp = appProvider.FindAppByIdAsync(command.AppId.Id); + var taskForSchema = schemas.FindSchemaByIdAsync(command.SchemaId.Id); + + await Task.WhenAll(taskForApp, taskForSchema); + + var context = new ContentOperationContext(); + + context.appEntity = taskForApp.Result; + context.assetRepository = assetRepository; + context.contentRepository = contentRepository; + context.content = content; + context.command = command; + context.message = message; + context.schemaEntity = taskForSchema.Result; + context.scriptEngine = scriptEngine; + + return context; + } + + public Task EnrichAsync() + { + if (command is ContentDataCommand dataCommand) + { + dataCommand.Data.Enrich(schemaEntity.SchemaDef, appEntity.PartitionResolver); + } + + return TaskHelper.Done; + } + + public async Task ValidateAsync(bool partial) + { + if (command is ContentDataCommand dataCommand) + { + var errors = new List(); + + var appId = command.AppId.Id; + + var ctx = + new ValidationContext( + (contentIds, schemaId) => + { + return contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); + }, + assetIds => + { + return assetRepository.QueryNotFoundAsync(appId, assetIds.ToList()); + }); + + if (partial) + { + await dataCommand.Data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver, errors); + } + else + { + await dataCommand.Data.ValidateAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver, errors); + } + + if (errors.Count > 0) + { + throw new ValidationException(message(), errors.ToArray()); + } + } + } + + public Task ExecuteScriptAndTransformAsync(Func script, object operation) + { + if (command is ContentDataCommand dataCommand) + { + var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString(), Data = dataCommand.Data }; + + dataCommand.Data = scriptEngine.ExecuteAndTransform(ctx, script(schemaEntity)); + } + + return TaskHelper.Done; + } + + public Task ExecuteScriptAsync(Func script, object operation) + { + var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString() }; + + scriptEngine.Execute(ctx, script(schemaEntity)); + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs index 28b7736ce..06178cdb1 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Write.Contents.Guards }); } - public static void CanCreate(UpdateContent command) + public static void CanUpdate(UpdateContent command) { Guard.NotNull(command, nameof(command)); @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Write.Contents.Guards }); } - public static void CanCreate(PatchContent command) + public static void CanPatch(PatchContent command) { Guard.NotNull(command, nameof(command)); @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Write.Contents.Guards }); } - public static void CanChangeStatus(Status status, ChangeContentStatus command) + public static void CanChangeContentStatus(Status status, ChangeContentStatus command) { Guard.NotNull(command, nameof(command)); @@ -65,5 +65,10 @@ namespace Squidex.Domain.Apps.Write.Contents.Guards } }); } + + public static void CanDelete(DeleteContent command) + { + Guard.NotNull(command, nameof(command)); + } } } diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs index ed0e69533..6502794b5 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core; diff --git a/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs b/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs index a18f6c9ee..a2a78ef90 100644 --- a/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs +++ b/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Squidex.Infrastructure.Security; diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs similarity index 62% rename from src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs rename to src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs index 15a6ad8bc..807a46627 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs @@ -1,17 +1,16 @@ // ========================================================================== -// IAppContributorEntity.cs +// BsonJsonAttribute.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Core.Apps; +using System; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Infrastructure.MongoDb { - public interface IAppContributorEntity + public sealed class BsonJsonAttribute : Attribute { - AppContributorPermission Permission { get; } } } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs new file mode 100644 index 000000000..e6b485f82 --- /dev/null +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// BsonJsonConvention.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using System.Reflection; +using MongoDB.Bson.Serialization.Conventions; +using Newtonsoft.Json; + +namespace Squidex.Infrastructure.MongoDb +{ + public static class BsonJsonConvention + { + public static void Register(JsonSerializer serializer) + { + var pack = new ConventionPack(); + + var bsonSerializer = new JsonBsonSerializer(serializer); + + pack.AddMemberMapConvention("JsonBson", memberMap => + { + if (memberMap.MemberType.GetCustomAttributes().OfType().Any()) + { + memberMap.SetSerializer(bsonSerializer); + } + }); + + ConventionRegistry.Register("json", pack, t => true); + } + } +} diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index 845f523bb..da9268948 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -14,6 +14,22 @@ namespace Squidex.Infrastructure { public static class CollectionExtensions { + public static bool TryGetValue(this IReadOnlyDictionary values, TKey key, out TBase item) where TValue : TBase + { + if (values.TryGetValue(key, out var value)) + { + item = value; + + return true; + } + else + { + item = default(TBase); + + return false; + } + } + public static int SequentialHashCode(this IEnumerable collection) { return collection.SequentialHashCode(EqualityComparer.Default); diff --git a/src/Squidex.Infrastructure/DictionaryWrapper.cs b/src/Squidex.Infrastructure/DictionaryWrapper.cs deleted file mode 100644 index 609406085..000000000 --- a/src/Squidex.Infrastructure/DictionaryWrapper.cs +++ /dev/null @@ -1,84 +0,0 @@ -// ========================================================================== -// DictionaryWrapper.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Squidex.Infrastructure -{ - public sealed class DictionaryWrapper : IReadOnlyDictionary where TSuper : class, TValue where TValue : class - { - private readonly Func> inner; - - public DictionaryWrapper(Func> inner) - { - Guard.NotNull(inner, nameof(inner)); - - this.inner = inner; - } - - public IEnumerable Keys - { - get { return inner().Keys; } - } - - public IEnumerable Values - { - get { return inner().Values.OfType(); } - } - - public int Count - { - get { return inner().Count; } - } - - public TValue this[TKey key] - { - get { return inner()[key]; } - } - - public bool ContainsKey(TKey key) - { - return inner().ContainsKey(key); - } - - public bool TryGetValue(TKey key, out TValue value) - { - if (inner().TryGetValue(key, out var temp)) - { - value = temp as TValue; - - return value != null; - } - - value = null; - - return false; - } - - public IEnumerator> GetEnumerator() - { - return Enumerate().GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private IEnumerable> Enumerate() - { - foreach (var kvp in inner()) - { - yield return new KeyValuePair(kvp.Key, (TValue)kvp.Value); - } - } - } -} \ No newline at end of file diff --git a/src/Squidex/Config/Domain/Serializers.cs b/src/Squidex/Config/Domain/Serializers.cs index 185a1c3a2..9ab3b0040 100644 --- a/src/Squidex/Config/Domain/Serializers.cs +++ b/src/Squidex/Config/Domain/Serializers.cs @@ -12,12 +12,14 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using NodaTime; using NodaTime.Serialization.JsonNet; +using Squidex.Domain.Apps.Core.Apps.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.MongoDb; namespace Squidex.Config.Domain { @@ -32,8 +34,11 @@ namespace Squidex.Config.Domain settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry); settings.ContractResolver = new ConverterContractResolver( + new AppClientsConverter(), + new AppContributorsConverter(), new InstantConverter(), new LanguageConverter(), + new LanguagesConfigConverter(), new NamedGuidIdConverter(), new NamedLongIdConverter(), new NamedStringIdConverter(), @@ -59,6 +64,8 @@ namespace Squidex.Config.Domain TypeNameRegistry.Map(typeof(SquidexEvent).GetTypeInfo().Assembly); TypeNameRegistry.Map(typeof(NoopEvent).GetTypeInfo().Assembly); + BsonJsonConvention.Register(JsonSerializer.Create(SerializerSettings)); + ConfigureJson(SerializerSettings, TypeNameHandling.Auto); } diff --git a/src/Squidex/Config/Identity/LazyClientStore.cs b/src/Squidex/Config/Identity/LazyClientStore.cs index b60f4688e..2fb3a33ca 100644 --- a/src/Squidex/Config/Identity/LazyClientStore.cs +++ b/src/Squidex/Config/Identity/LazyClientStore.cs @@ -13,7 +13,7 @@ using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Stores; using Microsoft.Extensions.Options; -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Infrastructure; @@ -64,7 +64,7 @@ namespace Squidex.Config.Identity return client; } - private static Client CreateClientFromApp(string id, IAppClientEntity appClient) + private static Client CreateClientFromApp(string id, AppClient appClient) { return new Client { diff --git a/src/Squidex/Controllers/Api/Apps/AppContributorsController.cs b/src/Squidex/Controllers/Api/Apps/AppContributorsController.cs index cc14ca983..b6acd8d12 100644 --- a/src/Squidex/Controllers/Api/Apps/AppContributorsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppContributorsController.cs @@ -52,7 +52,7 @@ namespace Squidex.Controllers.Api.Apps [ApiCosts(1)] public IActionResult GetContributors(string app) { - var contributors = App.Contributors.Select(x => SimpleMapper.Map(x.Value, new ContributorDto { ContributorId = x.Key })).ToArray(); + var contributors = App.Contributors.Select(x => new ContributorDto { ContributorId = x.Key, Permission = x.Value }).ToArray(); var response = new ContributorsDto { Contributors = contributors, MaxContributors = appPlansProvider.GetPlanForApp(App).MaxContributors }; diff --git a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs index fcb38bc72..4d0ee698a 100644 --- a/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Controllers.Api.Apps.Models; -using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; diff --git a/src/Squidex/Controllers/Api/Apps/AppsController.cs b/src/Squidex/Controllers/Api/Apps/AppsController.cs index e1a39b542..dbb119dbd 100644 --- a/src/Squidex/Controllers/Api/Apps/AppsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppsController.cs @@ -61,7 +61,7 @@ namespace Squidex.Controllers.Api.Apps { var dto = SimpleMapper.Map(s, new AppDto()); - dto.Permission = s.Contributors[subject].Permission; + dto.Permission = s.Contributors[subject]; return dto; }).ToList(); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs index a45904268..3bd914770 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System.Collections.Immutable; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; @@ -48,11 +47,6 @@ namespace Squidex.Controllers.Api.Schemas.Models { var result = SimpleMapper.Map(this, new NumberFieldProperties()); - if (AllowedValues != null) - { - result.AllowedValues = ImmutableList.Create(AllowedValues); - } - return result; } } diff --git a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs index c36674309..32a3e00da 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System.Collections.Immutable; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; @@ -58,11 +57,6 @@ namespace Squidex.Controllers.Api.Schemas.Models { var result = SimpleMapper.Map(this, new StringFieldProperties()); - if (AllowedValues != null) - { - result.AllowedValues = ImmutableList.Create(AllowedValues); - } - return result; } } diff --git a/src/Squidex/Controllers/ContentApi/ContentsController.cs b/src/Squidex/Controllers/ContentApi/ContentsController.cs index edb868f80..763a7c6fb 100644 --- a/src/Squidex/Controllers/ContentApi/ContentsController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentsController.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Controllers.ContentApi.Models; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Read.Contents.GraphQL; using Squidex.Domain.Apps.Write.Contents; diff --git a/src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs b/src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs index 094b7c335..82840cb94 100644 --- a/src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs +++ b/src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs @@ -13,8 +13,8 @@ using NJsonSchema; using NSwag; using Squidex.Config; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.GenerateJsonSchema; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.Schemas.JsonSchema; using Squidex.Infrastructure; using Squidex.Pipeline.Swagger; using Squidex.Shared.Identity; diff --git a/src/Squidex/Pipeline/AppPermissionAttribute.cs b/src/Squidex/Pipeline/AppPermissionAttribute.cs index 5abfbf000..82517dd38 100644 --- a/src/Squidex/Pipeline/AppPermissionAttribute.cs +++ b/src/Squidex/Pipeline/AppPermissionAttribute.cs @@ -96,9 +96,9 @@ namespace Squidex.Pipeline { var subjectId = user.FindFirst(OpenIdClaims.Subject)?.Value; - if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var contributor)) + if (subjectId != null && app.Contributors.TryGetValue(subjectId, out var permission)) { - return contributor.Permission.ToAppPermission(); + return permission.ToAppPermission(); } return null; diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index d7f56a3ee..a7ffd5aa4 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -23,12 +23,14 @@ - + PreserveNewest + + diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs new file mode 100644 index 000000000..99a38afec --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs @@ -0,0 +1,103 @@ +// ========================================================================== +// AppClientsTests.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 AppClientsTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + private readonly AppClients sut = new AppClients(); + + public AppClientsTests() + { + sut.Add("1", "my-secret"); + } + + [Fact] + public void Should_assign_client() + { + sut.Add("2", "my-secret"); + + sut["2"].ShouldBeEquivalentTo(new AppClient("2", "my-secret", AppClientPermission.Editor)); + } + + [Fact] + public void Should_assign_client_with_permission() + { + sut.Add("2", new AppClient("my-name", "my-secret", AppClientPermission.Reader)); + + sut["2"].ShouldBeEquivalentTo(new AppClient("my-name", "my-secret", AppClientPermission.Reader)); + } + + [Fact] + public void Should_throw_exception_if_assigning_client_with_same_id() + { + sut.Add("2", "my-secret"); + + Assert.Throws(() => sut.Add("2", "my-secret")); + } + + [Fact] + public void Should_rename_client() + { + sut["1"].Rename("my-name"); + + sut["1"].ShouldBeEquivalentTo(new AppClient("my-name", "my-secret", AppClientPermission.Editor)); + } + + [Fact] + public void Should_update_client() + { + sut["1"].Update(AppClientPermission.Reader); + + sut["1"].ShouldBeEquivalentTo(new AppClient("1", "my-secret", AppClientPermission.Reader)); + } + + [Fact] + public void Should_revoke_client() + { + sut.Revoke("1"); + + Assert.Empty(sut); + } + + [Fact] + public void Should_do_nothing_if_client_to_revoke_not_found() + { + sut.Revoke("2"); + + Assert.Single(sut); + } + + [Fact] + public void Should_serialize_and_deserialize() + { + sut.Add("2", "my-secret"); + sut.Add("3", "my-secret"); + sut.Add("4", "my-secret"); + + sut["3"].Update(AppClientPermission.Editor); + + sut["3"].Rename("My Client 3"); + sut["2"].Rename("My Client 2"); + + sut.Revoke("4"); + + var appClients = JToken.FromObject(sut, serializer).ToObject(serializer); + + appClients.ShouldBeEquivalentTo(sut); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs new file mode 100644 index 000000000..e56b792a9 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs @@ -0,0 +1,70 @@ +// ========================================================================== +// AppContributorsTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +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 AppContributorsTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + private readonly AppContributors sut = new AppContributors(); + + [Fact] + public void Should_assign_new_contributor() + { + sut.Assign("1", AppContributorPermission.Developer); + sut.Assign("2", AppContributorPermission.Editor); + + Assert.Equal(AppContributorPermission.Developer, sut["1"]); + Assert.Equal(AppContributorPermission.Editor, sut["2"]); + } + + [Fact] + public void Should_replace_contributor_if_already_exists() + { + sut.Assign("1", AppContributorPermission.Developer); + sut.Assign("1", AppContributorPermission.Owner); + + Assert.Equal(AppContributorPermission.Owner, sut["1"]); + } + + [Fact] + public void Should_remove_contributor() + { + sut.Assign("1", AppContributorPermission.Developer); + sut.Remove("1"); + + Assert.Empty(sut); + } + + [Fact] + public void Should_do_nothing_if_contributor_to_remove_not_found() + { + sut.Remove("2"); + + Assert.Empty(sut); + } + + [Fact] + public void Should_serialize_and_deserialize() + { + sut.Assign("1", AppContributorPermission.Developer); + sut.Assign("2", AppContributorPermission.Editor); + sut.Assign("3", AppContributorPermission.Owner); + + var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + + serialized.ShouldBeEquivalentTo(sut); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs new file mode 100644 index 000000000..c32406f9f --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// AppPlanTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Apps +{ + public class AppPlanTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + + [Fact] + public void Should_serialize_and_deserialize() + { + var sut = new AppPlan(new RefToken("user", "Me"), "free"); + + var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + + serialized.ShouldBeEquivalentTo(sut); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs new file mode 100644 index 000000000..cd5ae5eb9 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// LanguagesConfigJsonTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Apps +{ + public class LanguagesConfigJsonTests + { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); + + [Fact] + public void Should_serialize_and_deserialize() + { + var sut = LanguagesConfig.Build( + new LanguageConfig(Language.EN), + new LanguageConfig(Language.DE, true, Language.EN), + new LanguageConfig(Language.IT, false, Language.DE)); + + var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + + serialized.ShouldBeEquivalentTo(sut); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/LanguagesConfigTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Core.Tests/Model/LanguagesConfigTests.cs rename to tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs index d801a9810..b0830d366 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/LanguagesConfigTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs @@ -11,10 +11,11 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using FluentAssertions; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Core.Model +namespace Squidex.Domain.Apps.Core.Model.Apps { public class LanguagesConfigTests { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs index cb4b36c77..f9fe1e6f8 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs @@ -59,6 +59,23 @@ namespace Squidex.Domain.Apps.Core.Model.Contents Assert.Equal(expected, actual); } + [Fact] + public void Should_return_same_content_if_merging_same_references() + { + var source = + new NamedContentData() + .AddField("field1", + new ContentFieldData() + .AddValue("iv", 1)) + .AddField("field2", + new ContentFieldData() + .AddValue("de", 2)); + + var actual = source.MergeInto(source); + + Assert.Same(source, actual); + } + [Fact] public void Should_merge_two_name_models() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs deleted file mode 100644 index 941c85d5e..000000000 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ========================================================================== -// JsonSerializerTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.Schemas.Json; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; -using Xunit; - -namespace Squidex.Domain.Apps.Core.Model.Schemas.Json -{ - public class JsonSerializerTests - { - private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); - private readonly JsonSerializer serializer; - private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry(); - - public JsonSerializerTests() - { - serializerSettings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry); - - serializerSettings.ContractResolver = new ConverterContractResolver( - new InstantConverter(), - new LanguageConverter(), - new NamedGuidIdConverter(), - new NamedLongIdConverter(), - new NamedStringIdConverter(), - new RefTokenConverter(), - new SchemaConverter(new FieldRegistry(typeNameRegistry)), - new StringEnumConverter()); - - serializerSettings.TypeNameHandling = TypeNameHandling.Auto; - - serializer = JsonSerializer.Create(serializerSettings); - } - - [Fact] - public void Should_serialize_and_deserialize_schema() - { - var schemaSource = TestData.MixedSchema(); - var schemaTarget = JToken.FromObject(schemaSource, serializer).ToObject(serializer); - - schemaTarget.ShouldBeEquivalentTo(schemaSource); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs new file mode 100644 index 000000000..02f805529 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs @@ -0,0 +1,91 @@ +// ========================================================================== +// SchemaFieldTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Schemas; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Model.Schemas +{ + public class SchemaFieldTests + { + private readonly NumberField sut = new NumberField(1, "my-field", Partitioning.Invariant); + + [Fact] + public void Should_instantiate_field() + { + Assert.Equal("my-field", sut.Name); + } + + [Fact] + public void Should_throw_exception_if_creating_field_with_invalid_name() + { + Assert.Throws(() => new NumberField(1, string.Empty, Partitioning.Invariant)); + } + + [Fact] + public void Should_hide_field() + { + sut.Hide(); + sut.Hide(); + + Assert.True(sut.IsHidden); + } + + [Fact] + public void Should_show_field() + { + sut.Hide(); + sut.Show(); + sut.Show(); + + Assert.False(sut.IsHidden); + } + + [Fact] + public void Should_disable_field() + { + sut.Disable(); + sut.Disable(); + + Assert.True(sut.IsDisabled); + } + + [Fact] + public void Should_enable_field() + { + sut.Disable(); + sut.Enable(); + sut.Enable(); + + Assert.False(sut.IsDisabled); + } + + [Fact] + public void Should_lock_field() + { + sut.Lock(); + + Assert.True(sut.IsLocked); + } + + [Fact] + public void Should_update_field() + { + sut.Update(new NumberFieldProperties { Hints = "my-hints" }); + + Assert.Equal("my-hints", sut.RawProperties.Hints); + } + + [Fact] + public void Should_throw_exception_if_updating_with_invalid_properties_type() + { + Assert.Throws(() => sut.Update(new StringFieldProperties())); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index 8d5fbebfa..93b5006a9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -9,6 +9,9 @@ using System; using System.Collections.Generic; using System.Linq; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Xunit; @@ -16,6 +19,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas { public class SchemaTests { + private readonly JsonSerializer serializer = TestData.DefaultSerializer(); private readonly Schema sut = new Schema("my-schema"); [Fact] @@ -65,59 +69,11 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas } [Fact] - public void Should_hide_field() - { - AddNumberField(1); - - sut.FieldsById[1].Hide(); - sut.FieldsById[1].Hide(); - - Assert.True(sut.FieldsById[1].IsHidden); - } - - [Fact] - public void Should_show_field() - { - AddNumberField(1); - - sut.FieldsById[1].Hide(); - sut.FieldsById[1].Show(); - sut.FieldsById[1].Show(); - - Assert.False(sut.FieldsById[1].IsHidden); - } - - [Fact] - public void Should_disable_field() - { - AddNumberField(1); - - sut.FieldsById[1].Disable(); - sut.FieldsById[1].Disable(); - - Assert.True(sut.FieldsById[1].IsDisabled); - } - - [Fact] - public void Should_enable_field() - { - AddNumberField(1); - - sut.FieldsById[1].Disable(); - sut.FieldsById[1].Enable(); - sut.FieldsById[1].Enable(); - - Assert.False(sut.FieldsById[1].IsDisabled); - } - - [Fact] - public void Should_lock_field() + public void Should_throw_exception_if_updating_with_invalid_properties_type() { AddNumberField(1); - sut.FieldsById[1].Lock(); - - Assert.True(sut.FieldsById[1].IsLocked); + Assert.Throws(() => sut.FieldsById[1].Update(new StringFieldProperties())); } [Fact] @@ -140,24 +96,6 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas Assert.Empty(sut.FieldsById); } - [Fact] - public void Should_update_field() - { - AddNumberField(1); - - sut.FieldsById[1].Update(new NumberFieldProperties { Hints = "my-hints" }); - - Assert.Equal("my-hints", sut.FieldsById[1].RawProperties.Hints); - } - - [Fact] - public void Should_throw_exception_if_updating_with_invalid_properties_type() - { - AddNumberField(1); - - Assert.Throws(() => sut.FieldsById[1].Update(new StringFieldProperties())); - } - [Fact] public void Should_publish_schema() { @@ -205,6 +143,15 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas Assert.Throws(() => sut.ReorderFields(new List { 1, 4 })); } + [Fact] + public void Should_serialize_and_deserialize_schema() + { + var schemaSource = TestData.MixedSchema(); + var schemaTarget = JToken.FromObject(schemaSource, serializer).ToObject(serializer); + + schemaTarget.ShouldBeEquivalentTo(schemaSource); + } + private NumberField AddNumberField(int id) { var field = new NumberField(id, $"my-field-{id}", Partitioning.Invariant); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index a6c436d1a..663e00cbd 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -6,12 +6,11 @@ // All rights reserved. // ========================================================================== -using System; using System.Collections.Generic; using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs index 5f2b899d0..25b650970 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs @@ -9,6 +9,7 @@ using System; using Newtonsoft.Json.Linq; using NodaTime; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Schemas; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index 7f4af1cd6..1c69bdb85 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs index 9bc7060ab..e76471ee1 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Infrastructure; using Xunit; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs index 508e88d38..b786f90a2 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs @@ -7,6 +7,7 @@ // ========================================================================== using NJsonSchema; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.GenerateJsonSchema; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index 7376cd51c..9b0a35e5a 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index 33978a938..955fcdc74 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -7,7 +7,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index eda35ca87..c1103da56 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -7,7 +7,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading.Tasks; using FluentAssertions; using Newtonsoft.Json.Linq; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs index 0930c69da..15b4449a0 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs @@ -6,12 +6,45 @@ // All rights reserved. // ========================================================================== +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Squidex.Domain.Apps.Core.Apps.Json; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Schemas.Json; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Core { public static class TestData { + public static JsonSerializer DefaultSerializer() + { + var typeNameRegistry = new TypeNameRegistry(); + + var serializerSettings = new JsonSerializerSettings + { + SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry), + + ContractResolver = new ConverterContractResolver( + new AppClientsConverter(), + new AppContributorsConverter(), + new InstantConverter(), + new LanguageConverter(), + new LanguagesConfigConverter(), + new NamedGuidIdConverter(), + new NamedLongIdConverter(), + new NamedStringIdConverter(), + new RefTokenConverter(), + new SchemaConverter(new FieldRegistry(typeNameRegistry)), + new StringEnumConverter()), + + TypeNameHandling = TypeNameHandling.Auto + }; + + return JsonSerializer.Create(serializerSettings); + } + public static Schema MixedSchema() { var inv = Partitioning.Invariant; diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs index 2a4419573..a32193a3d 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs +++ b/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs @@ -37,28 +37,7 @@ namespace Squidex.Domain.Apps.Read.Contents { private static readonly Guid schemaId = Guid.NewGuid(); private static readonly Guid appId = Guid.NewGuid(); - - private readonly Schema schemaDef = - Schema.Create("my-schema", new SchemaProperties()) - .AddField(new JsonField(1, "my-json", Partitioning.Invariant, - new JsonFieldProperties())) - .AddField(new StringField(2, "my-string", Partitioning.Language, - new StringFieldProperties())) - .AddField(new NumberField(3, "my-number", Partitioning.Invariant, - new NumberFieldProperties())) - .AddField(new AssetsField(4, "my-assets", Partitioning.Invariant, - new AssetsFieldProperties())) - .AddField(new BooleanField(5, "my-boolean", Partitioning.Invariant, - new BooleanFieldProperties())) - .AddField(new DateTimeField(6, "my-datetime", Partitioning.Invariant, - new DateTimeFieldProperties())) - .AddField(new ReferencesField(7, "my-references", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = schemaId })) - .AddField(new ReferencesField(9, "my-invalid", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })) - .AddField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant, - new GeolocationFieldProperties())); - + private readonly Schema schemaDef = new Schema("my-schema"); private readonly IContentQueryService contentQuery = A.Fake(); private readonly ISchemaRepository schemaRepository = A.Fake(); private readonly IAssetRepository assetRepository = A.Fake(); @@ -70,6 +49,33 @@ namespace Squidex.Domain.Apps.Read.Contents public GraphQLTests() { + schemaDef.AddField(new JsonField(1, "my-json", Partitioning.Invariant, + new JsonFieldProperties())); + + schemaDef.AddField(new StringField(2, "my-string", Partitioning.Language, + new StringFieldProperties())); + + schemaDef.AddField(new NumberField(3, "my-number", Partitioning.Invariant, + new NumberFieldProperties())); + + schemaDef.AddField(new AssetsField(4, "my-assets", Partitioning.Invariant, + new AssetsFieldProperties())); + + schemaDef.AddField(new BooleanField(5, "my-boolean", Partitioning.Invariant, + new BooleanFieldProperties())); + + schemaDef.AddField(new DateTimeField(6, "my-datetime", Partitioning.Invariant, + new DateTimeFieldProperties())); + + schemaDef.AddField(new ReferencesField(7, "my-references", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = schemaId })); + + schemaDef.AddField(new ReferencesField(9, "my-invalid", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })); + + schemaDef.AddField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant, + new GeolocationFieldProperties())); + A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.PartitionResolver).Returns(x => InvariantPartitioning.Instance); diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs index c34b87fbc..f256b7d47 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs @@ -7,7 +7,6 @@ // ========================================================================== using System; -using System.Collections.Immutable; using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -15,6 +14,7 @@ using Microsoft.OData.Edm; using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Contents.Edm; @@ -29,29 +29,11 @@ namespace Squidex.Domain.Apps.Read.Contents { public class ODataQueryTests { - private readonly Schema schemaDef = - Schema.Create("user", new SchemaProperties { Hints = "The User" }) - .AddField(new StringField(1, "firstName", Partitioning.Language, - new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = new[] { "1", "2" }.ToImmutableList() })) - .AddField(new StringField(2, "lastName", Partitioning.Language, - new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input })) - .AddField(new BooleanField(3, "isAdmin", Partitioning.Invariant, - new BooleanFieldProperties())) - .AddField(new NumberField(4, "age", Partitioning.Invariant, - new NumberFieldProperties { MinValue = 1, MaxValue = 10 })) - .AddField(new DateTimeField(5, "birthday", Partitioning.Invariant, - new DateTimeFieldProperties())) - .AddField(new AssetsField(6, "pictures", Partitioning.Invariant, - new AssetsFieldProperties())) - .AddField(new ReferencesField(7, "friends", Partitioning.Invariant, - new ReferencesFieldProperties())) - .AddField(new StringField(8, "dashed-field", Partitioning.Invariant, - new StringFieldProperties())); - + private readonly Schema schemaDef = new Schema("user"); private readonly IBsonSerializerRegistry registry = BsonSerializer.SerializerRegistry; private readonly IBsonSerializer serializer = BsonSerializer.SerializerRegistry.GetSerializer(); private readonly IEdmModel edmModel; - private readonly LanguagesConfig languagesConfig = LanguagesConfig.Create(Language.EN, Language.DE); + private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); static ODataQueryTests() { @@ -60,6 +42,31 @@ namespace Squidex.Domain.Apps.Read.Contents public ODataQueryTests() { + schemaDef.Update(new SchemaProperties { Hints = "The User" }); + + schemaDef.AddField(new StringField(1, "firstName", Partitioning.Language, + new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = new[] { "1", "2" } })); + schemaDef.AddField(new StringField(2, "lastName", Partitioning.Language, + new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input })); + + schemaDef.AddField(new BooleanField(3, "isAdmin", Partitioning.Invariant, + new BooleanFieldProperties())); + + schemaDef.AddField(new NumberField(4, "age", Partitioning.Invariant, + new NumberFieldProperties { MinValue = 1, MaxValue = 10 })); + + schemaDef.AddField(new DateTimeField(5, "birthday", Partitioning.Invariant, + new DateTimeFieldProperties())); + + schemaDef.AddField(new AssetsField(6, "pictures", Partitioning.Invariant, + new AssetsFieldProperties())); + + schemaDef.AddField(new ReferencesField(7, "friends", Partitioning.Invariant, + new ReferencesFieldProperties())); + + schemaDef.AddField(new StringField(8, "dashed-field", Partitioning.Invariant, + new StringFieldProperties())); + var builder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions()))); var schema = A.Dummy(); diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs new file mode 100644 index 000000000..c10fc34d7 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs @@ -0,0 +1,140 @@ +// ========================================================================== +// GuardAppClientsTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Apps.Guards +{ + public class GuardAppClientsTests + { + private readonly AppClients clients = new AppClients(); + + [Fact] + public void CanAttach_should_throw_execption_if_client_id_is_null() + { + var command = new AttachClient(); + + Assert.Throws(() => GuardAppClients.CanAttach(clients, command)); + } + + [Fact] + public void CanAttach_should_throw_exception_if_client_already_exists() + { + var command = new AttachClient { Id = "android" }; + + clients.Add("android", "secret"); + + Assert.Throws(() => GuardAppClients.CanAttach(clients, command)); + } + + [Fact] + public void CanAttach_should_not_throw_exception_if_client_is_free() + { + var command = new AttachClient { Id = "ios" }; + + clients.Add("android", "secret"); + + GuardAppClients.CanAttach(clients, command); + } + + [Fact] + public void CanRevoke_should_throw_execption_if_client_id_is_null() + { + var command = new RevokeClient(); + + Assert.Throws(() => GuardAppClients.CanRevoke(clients, command)); + } + + [Fact] + public void CanRevoke_should_throw_exception_if_client_is_not_found() + { + var command = new RevokeClient { Id = "ios" }; + + Assert.Throws(() => GuardAppClients.CanRevoke(clients, command)); + } + + [Fact] + public void CanRevoke_should_not_throw_exception_if_client_is_found() + { + var command = new RevokeClient { Id = "ios" }; + + clients.Add("ios", "secret"); + + GuardAppClients.CanRevoke(clients, command); + } + + [Fact] + public void CanUpdate_should_throw_execption_if_client_id_is_null() + { + var command = new UpdateClient(); + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_throw_exception_if_client_is_not_found() + { + var command = new UpdateClient { Id = "ios", Name = "iOS" }; + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_throw_exception_if_client_has_no_name_and_permission() + { + var command = new UpdateClient { Id = "ios" }; + + clients.Add("ios", "secret"); + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_throw_exception_if_client_has_invalid_permission() + { + var command = new UpdateClient { Id = "ios", Permission = (AppClientPermission)10 }; + + clients.Add("ios", "secret"); + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_throw_exception_if_client_has_same_name() + { + var command = new UpdateClient { Id = "ios", Name = "ios" }; + + clients.Add("ios", "secret"); + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_throw_exception_if_client_has_same_permission() + { + var command = new UpdateClient { Id = "ios", Permission = AppClientPermission.Editor }; + + clients.Add("ios", "secret"); + + Assert.Throws(() => GuardAppClients.CanUpdate(clients, command)); + } + + [Fact] + public void UpdateClient_should_not_throw_exception_if_command_is_valid() + { + var command = new UpdateClient { Id = "ios", Name = "iOS", Permission = AppClientPermission.Reader }; + + clients.Add("ios", "secret"); + + GuardAppClients.CanUpdate(clients, command); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs index f9d007afd..fc59e17a5 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs @@ -21,6 +21,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { private readonly IUserResolver users = A.Fake(); private readonly IAppLimitsPlan appPlan = A.Fake(); + private readonly AppContributors contributors = new AppContributors(); public GuardAppContributorsTests() { @@ -36,8 +37,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AssignContributor(); - var contributors = new AppContributors(); - return Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan)); } @@ -46,8 +45,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AssignContributor { ContributorId = "1", Permission = (AppContributorPermission)10 }; - var contributors = new AppContributors(); - return Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan)); } @@ -56,8 +53,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AssignContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Owner); return Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan)); @@ -71,8 +66,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards var command = new AssignContributor { ContributorId = "1", Permission = (AppContributorPermission)10 }; - var contributors = new AppContributors(); - return Assert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors, command, users, appPlan)); } @@ -84,8 +77,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards var command = new AssignContributor { ContributorId = "3" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Owner); contributors.Assign("2", AppContributorPermission.Editor); @@ -97,8 +88,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AssignContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - return GuardAppContributors.CanAssign(contributors, command, users, appPlan); } @@ -107,8 +96,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AssignContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Editor); return GuardAppContributors.CanAssign(contributors, command, users, appPlan); @@ -122,8 +109,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards var command = new AssignContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Editor); contributors.Assign("2", AppContributorPermission.Editor); @@ -135,8 +120,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveContributor(); - var contributors = new AppContributors(); - Assert.Throws(() => GuardAppContributors.CanRemove(contributors, command)); } @@ -145,8 +128,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - Assert.Throws(() => GuardAppContributors.CanRemove(contributors, command)); } @@ -155,8 +136,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Owner); contributors.Assign("2", AppContributorPermission.Editor); @@ -168,8 +147,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveContributor { ContributorId = "1" }; - var contributors = new AppContributors(); - contributors.Assign("1", AppContributorPermission.Owner); contributors.Assign("2", AppContributorPermission.Owner); diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs index a03087358..02e3ce614 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs @@ -7,7 +7,7 @@ // ========================================================================== using System.Collections.Generic; -using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; using Xunit; @@ -16,13 +16,13 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { public class GuardAppLanguagesTests { + private readonly LanguagesConfig languages = LanguagesConfig.Build(Language.DE); + [Fact] public void CanAddLanguage_should_throw_exception_if_language_is_null() { var command = new AddLanguage(); - var languages = LanguagesConfig.Build(Language.DE); - Assert.Throws(() => GuardAppLanguages.CanAdd(languages, command)); } @@ -31,8 +31,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AddLanguage { Language = Language.DE }; - var languages = LanguagesConfig.Build(Language.DE); - Assert.Throws(() => GuardAppLanguages.CanAdd(languages, command)); } @@ -41,8 +39,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new AddLanguage { Language = Language.EN }; - var languages = LanguagesConfig.Build(Language.DE); - GuardAppLanguages.CanAdd(languages, command); } @@ -51,9 +47,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveLanguage(); - var languages = LanguagesConfig.Build(Language.DE); - - Assert.Throws(() => GuardAppLanguages.CanRemove(languages, command)); + Assert.Throws(() => GuardAppLanguages.CanRemove(languages, command)); } [Fact] @@ -61,8 +55,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveLanguage { Language = Language.EN }; - var languages = LanguagesConfig.Build(Language.DE); - Assert.Throws(() => GuardAppLanguages.CanRemove(languages, command)); } @@ -71,8 +63,6 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveLanguage { Language = Language.DE }; - var languages = LanguagesConfig.Build(Language.DE); - Assert.Throws(() => GuardAppLanguages.CanRemove(languages, command)); } @@ -81,17 +71,27 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new RemoveLanguage { Language = Language.EN }; - var languages = LanguagesConfig.Build(Language.DE, Language.EN); + languages.Set(new LanguageConfig(Language.EN)); GuardAppLanguages.CanRemove(languages, command); } + [Fact] + public void CanUpdateLanguage_should_throw_exception_if_language_is_null() + { + var command = new UpdateLanguage(); + + languages.Set(new LanguageConfig(Language.EN)); + + Assert.Throws(() => GuardAppLanguages.CanUpdate(languages, command)); + } + [Fact] public void CanUpdateLanguage_should_throw_exception_if_language_is_optional_and_master() { var command = new UpdateLanguage { Language = Language.DE, IsOptional = true }; - var languages = LanguagesConfig.Build(Language.DE, Language.EN); + languages.Set(new LanguageConfig(Language.EN)); Assert.Throws(() => GuardAppLanguages.CanUpdate(languages, command)); } @@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.IT } }; - var languages = LanguagesConfig.Build(Language.DE, Language.EN); + languages.Set(new LanguageConfig(Language.EN)); Assert.Throws(() => GuardAppLanguages.CanUpdate(languages, command)); } @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new UpdateLanguage { Language = Language.IT }; - var languages = LanguagesConfig.Build(Language.DE, Language.EN); + languages.Set(new LanguageConfig(Language.EN)); Assert.Throws(() => GuardAppLanguages.CanUpdate(languages, command)); } @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Write.Apps.Guards { var command = new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.EN } }; - var languages = LanguagesConfig.Build(Language.DE, Language.EN); + languages.Set(new LanguageConfig(Language.EN)); GuardAppLanguages.CanUpdate(languages, command); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs index 9d02d19b5..3ea088770 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -11,6 +11,7 @@ using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs new file mode 100644 index 000000000..b0f78570d --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs @@ -0,0 +1,99 @@ +// ========================================================================== +// GuardContentTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Write.Contents.Guards; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Contents.Guard +{ + public class GuardContentTests + { + [Fact] + public void CanCreate_should_throw_exception_if_data_is_null() + { + var command = new CreateContent(); + + Assert.Throws(() => GuardContent.CanCreate(command)); + } + + [Fact] + public void CanCreate_should_not_throw_exception_if_data_is_not_null() + { + var command = new CreateContent { Data = new NamedContentData() }; + + GuardContent.CanCreate(command); + } + + [Fact] + public void CanUpdate_should_throw_exception_if_data_is_null() + { + var command = new UpdateContent(); + + Assert.Throws(() => GuardContent.CanUpdate(command)); + } + + [Fact] + public void CanUpdate_should_not_throw_exception_if_data_is_not_null() + { + var command = new UpdateContent { Data = new NamedContentData() }; + + GuardContent.CanUpdate(command); + } + + [Fact] + public void CanPatch_should_throw_exception_if_data_is_null() + { + var command = new PatchContent(); + + Assert.Throws(() => GuardContent.CanPatch(command)); + } + + [Fact] + public void CanPatch_should_not_throw_exception_if_data_is_not_null() + { + var command = new PatchContent { Data = new NamedContentData() }; + + GuardContent.CanPatch(command); + } + + [Fact] + public void CanChangeContentStatus_should_throw_exception_if_status_not_valid() + { + var command = new ChangeContentStatus { Status = (Status)10 }; + + Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Archived, command)); + } + + [Fact] + public void CanChangeContentStatus_should_throw_exception_if_status_flow_not_valid() + { + var command = new ChangeContentStatus { Status = Status.Published }; + + Assert.Throws(() => GuardContent.CanChangeContentStatus(Status.Archived, command)); + } + + [Fact] + public void CanChangeContentStatus_not_should_throw_exception_if_status_flow_valid() + { + var command = new ChangeContentStatus { Status = Status.Published }; + + GuardContent.CanChangeContentStatus(Status.Draft, command); + } + + [Fact] + public void CanPatch_should_not_throw_exception() + { + var command = new DeleteContent(); + + GuardContent.CanDelete(command); + } + } +}