Browse Source

Json support

pull/152/head
Sebastian Stehle 8 years ago
parent
commit
2b0681ef67
  1. 1
      Squidex.sln.DotSettings
  2. 20
      src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs
  3. 16
      src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs
  4. 14
      src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs
  5. 50
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs
  6. 50
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs
  7. 39
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonAppClient.cs
  8. 40
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguageConfig.cs
  9. 52
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs
  10. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs
  11. 36
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  12. 63
      src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs
  13. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  14. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs
  15. 1
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  16. 1
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs
  17. 1
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs
  18. 5
      src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs
  19. 6
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs
  20. 5
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs
  21. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  22. 16
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs
  23. 14
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs
  24. 12
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs
  25. 6
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  26. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaEntity.cs
  27. 6
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  28. 21
      src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs
  29. 29
      src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs
  30. 1
      src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs
  31. 2
      src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs
  32. 102
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs
  33. 8
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs
  34. 14
      src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs
  35. 2
      src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs
  36. 2
      src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs
  37. 111
      src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs
  38. 134
      src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs
  39. 11
      src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs
  40. 1
      src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs
  41. 1
      src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs
  42. 9
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs
  43. 35
      src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs
  44. 16
      src/Squidex.Infrastructure/CollectionExtensions.cs
  45. 84
      src/Squidex.Infrastructure/DictionaryWrapper.cs
  46. 7
      src/Squidex/Config/Domain/Serializers.cs
  47. 4
      src/Squidex/Config/Identity/LazyClientStore.cs
  48. 2
      src/Squidex/Controllers/Api/Apps/AppContributorsController.cs
  49. 2
      src/Squidex/Controllers/Api/Apps/AppLanguagesController.cs
  50. 2
      src/Squidex/Controllers/Api/Apps/AppsController.cs
  51. 6
      src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs
  52. 6
      src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs
  53. 1
      src/Squidex/Controllers/ContentApi/ContentsController.cs
  54. 2
      src/Squidex/Controllers/ContentApi/Generator/SchemaSwaggerGenerator.cs
  55. 4
      src/Squidex/Pipeline/AppPermissionAttribute.cs
  56. 4
      src/Squidex/Squidex.csproj
  57. 103
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs
  58. 70
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsTests.cs
  59. 32
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs
  60. 35
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs
  61. 3
      tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigTests.cs
  62. 17
      tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs
  63. 55
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs
  64. 91
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs
  65. 83
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs
  66. 3
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs
  67. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs
  68. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  69. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs
  70. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs
  71. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs
  72. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs
  73. 1
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs
  74. 33
      tests/Squidex.Domain.Apps.Core.Tests/TestData.cs
  75. 50
      tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs
  76. 49
      tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs
  77. 140
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs
  78. 25
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs
  79. 38
      tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs
  80. 1
      tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs
  81. 99
      tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs

1
Squidex.sln.DotSettings

@ -17,6 +17,7 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Header/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Header"&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>

20
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)

16
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<string, AppClient>
{
private readonly Dictionary<string, AppClient> clients = new Dictionary<string, AppClient>();
public IReadOnlyDictionary<string, AppClient> 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);
}
}
}

14
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<string, AppContributorPermission>
{
private readonly Dictionary<string, AppContributorPermission> contributors = new Dictionary<string, AppContributorPermission>();
public IReadOnlyDictionary<string, AppContributorPermission> 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);
}
}
}

50
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<string, JsonAppClient>(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<Dictionary<string, JsonAppClient>>(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);
}
}
}

50
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<string, AppContributorPermission>(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<Dictionary<string, AppContributorPermission>>(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);
}
}
}

39
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);
}
}
}

40
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);
}
}
}

52
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<string, JsonLanguageConfig>(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<Dictionary<string, JsonLanguageConfig>>(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);
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/LanguageConfig.cs → 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
{

36
src/Squidex.Domain.Apps.Core.Model/LanguagesConfig.cs → 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<LanguageConfig> Configs
{
get { return state.Languages.Values; }
}
public int Count
{
get { return state.Languages.Count; }
}
private LanguagesConfig(ICollection<LanguageConfig> 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

63
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<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
private readonly Dictionary<TKey, TValue> inner = new Dictionary<TKey, TValue>();
public TValue this[TKey key]
{
get { return inner[key]; }
}
public IEnumerable<TKey> Keys
{
get { return inner.Keys; }
}
public IEnumerable<TValue> Values
{
get { return inner.Values; }
}
public int Count
{
get { return inner.Count; }
}
protected Dictionary<TKey, TValue> 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<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return inner.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return inner.GetEnumerator();
}
}
}

2
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

2
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

1
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;

1
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;

1
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;

5
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);
}

6
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

5
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<MongoAppEntity>, IAppRepository, IEventConsumer
{
public MongoAppRepository(IMongoDatabase database, JsonSerializer serializer)
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
BsonSerializer.RegisterSerializer(new JsonBsonSerializer(serializer));
}
protected override string CollectionName()

2
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();
});
}
}

16
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<Guid> ToReferencedIds(this IdContentData data, Schema schema)
{
return data.GetReferencedIds(schema).ToList();
}
public static NamedContentData ToData(this BsonDocument document, Schema schema, List<Guid> deletedIds, JsonSerializer jsonSerializer)
public static NamedContentData ToData(this IdContentData idData, Schema schema, List<Guid> deletedIds)
{
return document
.ToJson()
.ToObject<IdContentData>(jsonSerializer)
.ToCleanedReferences(schema, new HashSet<Guid>(deletedIds ?? new List<Guid>()))
.ToNameModel(schema, true);
return idData.ToCleanedReferences(schema, new HashSet<Guid>(deletedIds)).ToNameModel(schema, true);
}
public static string ToFullText<T>(this ContentData<T> data)

14
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<Guid> ReferencedIds { get; set; }
@ -78,14 +73,19 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
[BsonElement("rd")]
public List<Guid> ReferencedIdsDeleted { get; set; } = new List<Guid>();
[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);
}
}
}

12
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<MongoContentEntity> 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<IReadOnlyList<IContentEntity>> 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<IContentEntity>().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;
}

6
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)

2
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; }
}
}

6
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()

21
src/Squidex.Domain.Apps.Read/Apps/IAppClientEntity.cs

@ -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; }
}
}

29
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<AppDomainObject>(context, a => a.AttachClient(command));
}
protected async Task On(AssignContributor command, CommandContext context)
{
await handler.UpdateAsync<AppDomainObject>(context, async a =>
@ -83,14 +78,34 @@ namespace Squidex.Domain.Apps.Write.Apps
});
}
protected Task On(AttachClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, a =>
{
GuardAppClients.CanAttach(a.Clients, command);
a.AttachClient(command);
});
}
protected Task On(UpdateClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, a => a.UpdateClient(command));
return handler.UpdateAsync<AppDomainObject>(context, a =>
{
GuardAppClients.CanUpdate(a.Clients, command);
a.UpdateClient(command);
});
}
protected Task On(RevokeClient command, CommandContext context)
{
return handler.UpdateAsync<AppDomainObject>(context, a => a.RevokeClient(command));
return handler.UpdateAsync<AppDomainObject>(context, a =>
{
GuardAppClients.CanRevoke(a.Clients, command);
a.RevokeClient(command);
});
}
protected Task On(AddLanguage command, CommandContext context)

1
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;

2
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

102
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;
}
}
}

8
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));
}

14
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))

2
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

2
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

111
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<ContentDomainObject>(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<ContentDomainObject>(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<ContentDomainObject>(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<ContentDomainObject>(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<ContentDomainObject>(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<string> message, bool partial)
private async Task<ContentOperationContext> CreateContext(ContentCommand command, ContentDomainObject content, Func<string> message)
{
var schemaErrors = new List<ValidationError>();
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;
}
}
}

134
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<string> message;
public static async Task<ContentOperationContext> CreateAsync(
IContentRepository contentRepository,
ContentDomainObject content,
ContentCommand command,
IAppProvider appProvider,
ISchemaProvider schemas,
IScriptEngine scriptEngine,
IAssetRepository assetRepository,
Func<string> 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<ValidationError>();
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<ISchemaEntity, string> 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<ISchemaEntity, string> 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;
}
}
}

11
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));
}
}
}

1
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;

1
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;

9
src/Squidex.Domain.Apps.Read/Apps/IAppContributorEntity.cs → 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; }
}
}

35
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<BsonJsonAttribute>().Any())
{
memberMap.SetSerializer(bsonSerializer);
}
});
ConventionRegistry.Register("json", pack, t => true);
}
}
}

16
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -14,6 +14,22 @@ namespace Squidex.Infrastructure
{
public static class CollectionExtensions
{
public static bool TryGetValue<TKey, TValue, TBase>(this IReadOnlyDictionary<TKey, TValue> 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<T>(this IEnumerable<T> collection)
{
return collection.SequentialHashCode(EqualityComparer<T>.Default);

84
src/Squidex.Infrastructure/DictionaryWrapper.cs

@ -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<TKey, TValue, TSuper> : IReadOnlyDictionary<TKey, TValue> where TSuper : class, TValue where TValue : class
{
private readonly Func<Dictionary<TKey, TSuper>> inner;
public DictionaryWrapper(Func<Dictionary<TKey, TSuper>> inner)
{
Guard.NotNull(inner, nameof(inner));
this.inner = inner;
}
public IEnumerable<TKey> Keys
{
get { return inner().Keys; }
}
public IEnumerable<TValue> Values
{
get { return inner().Values.OfType<TValue>(); }
}
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<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Enumerate().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private IEnumerable<KeyValuePair<TKey, TValue>> Enumerate()
{
foreach (var kvp in inner())
{
yield return new KeyValuePair<TKey, TValue>(kvp.Key, (TValue)kvp.Value);
}
}
}
}

7
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);
}

4
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
{

2
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 };

2
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;

2
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();

6
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;
}
}

6
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;
}
}

1
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;

2
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;

4
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;

4
src/Squidex/Squidex.csproj

@ -23,12 +23,14 @@
</ItemGroup>
<ItemGroup>
<None Update="dockerfile">
<None Update="dockerfile">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" />
<ProjectReference Include="..\Squidex.Domain.Users.MongoDb\Squidex.Domain.Users.MongoDb.csproj" />
<ProjectReference Include="..\Squidex.Domain.Users\Squidex.Domain.Users.csproj" />

103
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<ArgumentException>(() => 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<AppClients>(serializer);
appClients.ShouldBeEquivalentTo(sut);
}
}
}

70
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<AppContributors>(serializer);
serialized.ShouldBeEquivalentTo(sut);
}
}
}

32
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<AppPlan>(serializer);
serialized.ShouldBeEquivalentTo(sut);
}
}
}

35
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<LanguagesConfig>(serializer);
serialized.ShouldBeEquivalentTo(sut);
}
}
}

3
tests/Squidex.Domain.Apps.Core.Tests/Model/LanguagesConfigTests.cs → 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
{

17
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()
{

55
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/Json/JsonSerializerTests.cs

@ -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<Schema>(serializer);
schemaTarget.ShouldBeEquivalentTo(schemaSource);
}
}
}

91
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<ArgumentException>(() => 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<ArgumentException>(() => sut.Update(new StringFieldProperties()));
}
}
}

83
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<ArgumentException>(() => 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<ArgumentException>(() => 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<ArgumentException>(() => sut.ReorderFields(new List<long> { 1, 4 }));
}
[Fact]
public void Should_serialize_and_deserialize_schema()
{
var schemaSource = TestData.MixedSchema();
var schemaTarget = JToken.FromObject(schemaSource, serializer).ToObject<Schema>(serializer);
schemaTarget.ShouldBeEquivalentTo(schemaSource);
}
private NumberField AddNumberField(int id)
{
var field = new NumberField(id, $"my-field-{id}", Partitioning.Invariant);

3
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;

1
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;

1
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;

1
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;

1
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;

1
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;

1
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;

1
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;

33
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;

50
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<IContentQueryService>();
private readonly ISchemaRepository schemaRepository = A.Fake<ISchemaRepository>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
@ -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);

49
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<MongoContentEntity> serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>();
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<ISchemaEntity>();

140
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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => GuardAppClients.CanRevoke(clients, command));
}
[Fact]
public void CanRevoke_should_throw_exception_if_client_is_not_found()
{
var command = new RevokeClient { Id = "ios" };
Assert.Throws<DomainObjectNotFoundException>(() => 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<ValidationException>(() => 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<DomainObjectNotFoundException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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);
}
}
}

25
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<IUserResolver>();
private readonly IAppLimitsPlan appPlan = A.Fake<IAppLimitsPlan>();
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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<DomainObjectNotFoundException>(() => 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);

38
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<ValidationException>(() => 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<ValidationException>(() => 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<DomainObjectNotFoundException>(() => GuardAppLanguages.CanRemove(languages, command));
Assert.Throws<ValidationException>(() => 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<DomainObjectNotFoundException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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> { Language.IT } };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
languages.Set(new LanguageConfig(Language.EN));
Assert.Throws<ValidationException>(() => 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<DomainObjectNotFoundException>(() => 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> { Language.EN } };
var languages = LanguagesConfig.Build(Language.DE, Language.EN);
languages.Set(new LanguageConfig(Language.EN));
GuardAppLanguages.CanUpdate(languages, command);
}

1
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;

99
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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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<ValidationException>(() => 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);
}
}
}
Loading…
Cancel
Save