Browse Source

Upsert started.

pull/343/head
Sebastian Stehle 7 years ago
parent
commit
0b2bcb411d
  1. 7
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs
  2. 48
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs
  3. 2
      src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  4. 46
      src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs
  5. 8
      src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs
  6. 2
      src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs
  7. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs
  8. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs
  9. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs
  10. 4
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs
  11. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs
  12. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs
  13. 6
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs
  14. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs
  16. 6
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs
  17. 15
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs
  18. 49
      src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs
  19. 2
      src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs
  20. 2
      src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs
  21. 10
      src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs
  22. 4
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  23. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  24. 6
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  25. 2
      src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs
  26. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs
  27. 12
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs
  28. 14
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs
  29. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs
  30. 14
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ParentFieldCommand.cs
  31. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs
  32. 13
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs
  33. 101
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs
  34. 4
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs
  35. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs
  36. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaNestedField.cs
  37. 121
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  38. 21
      src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs
  39. 23
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs
  40. 129
      src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs
  41. 165
      src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs
  42. 4
      src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs
  43. 15
      src/Squidex.Domain.Apps.Events/Schemas/ParentFieldEvent.cs
  44. 15
      src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs
  45. 5
      src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs
  46. 2
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs
  47. 8
      src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs
  48. 4
      src/Squidex/Config/Domain/SerializationServices.cs
  49. 2
      src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs
  50. 2
      src/Squidex/Pipeline/UrlGenerator.cs
  51. 67
      tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs
  52. 2
      tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs
  53. 69
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs
  54. 3
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs
  55. 19
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  56. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs
  57. 16
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs
  58. 67
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  59. 13
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  60. 43
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs
  61. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs
  62. 78
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs
  63. 39
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs
  64. 49
      tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs
  65. 38
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs
  66. 97
      tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs
  67. 2
      tools/Migrate_01/Migrations/PopulateGrainIndexes.cs
  68. 106
      tools/Migrate_01/OldEvents/SchemaCreated.cs
  69. 65
      tools/Migrate_01/OldEvents/ScriptsConfigured.cs
  70. 5
      tools/Migrate_01/Rebuilder.cs

7
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs

@ -5,12 +5,19 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using System.Collections.Generic; using System.Collections.Generic;
using NamedIdStatic = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public static class FieldExtensions public static class FieldExtensions
{ {
public static NamedId<long> NamedId(this IField field)
{
return NamedIdStatic.Of(field.Id, field.Name);
}
public static Schema ReorderFields(this Schema schema, List<long> ids, long? parentId = null) public static Schema ReorderFields(this Schema schema, List<long> ids, long? parentId = null)
{ {
if (parentId != null) if (parentId != null)

48
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs

@ -12,58 +12,26 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public sealed class FieldRegistry public static class FieldRegistry
{ {
private readonly TypeNameRegistry typeNameRegistry; public static void Setup(TypeNameRegistry typeNameRegistry)
private readonly HashSet<Type> supportedFields = new HashSet<Type>();
public FieldRegistry(TypeNameRegistry typeNameRegistry)
{ {
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
this.typeNameRegistry = typeNameRegistry;
var types = typeof(FieldRegistry).Assembly.GetTypes().Where(x => x.BaseType == typeof(FieldProperties)); var types = typeof(FieldRegistry).Assembly.GetTypes().Where(x => x.BaseType == typeof(FieldProperties));
var supportedFields = new HashSet<Type>();
foreach (var type in types) foreach (var type in types)
{ {
RegisterField(type); if (supportedFields.Add(type))
{
typeNameRegistry.Map(type);
}
} }
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "References"); typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "References");
typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "DateTime"); typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "DateTime");
} }
private void RegisterField(Type type)
{
if (supportedFields.Add(type))
{
typeNameRegistry.Map(type);
}
}
public RootField CreateRootField(long id, string name, Partitioning partitioning, FieldProperties properties, IFieldSettings settings = null)
{
CheckProperties(properties);
return properties.CreateRootField(id, name, partitioning, settings);
}
public NestedField CreateNestedField(long id, string name, FieldProperties properties, IFieldSettings settings = null)
{
CheckProperties(properties);
return properties.CreateNestedField(id, name, settings);
}
private void CheckProperties(FieldProperties properties)
{
Guard.NotNull(properties, nameof(properties));
if (!supportedFields.Contains(properties.GetType()))
{
throw new InvalidOperationException($"The field property '{properties.GetType()}' is not supported.");
}
}
} }
} }

2
src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
} }
[Pure] [Pure]
public Schema MoveTo(string category) public Schema ChangeCategory(string category)
{ {
return Clone(clone => return Clone(clone =>
{ {

46
src/Squidex.Domain.Apps.Core.Model/Schemas/Scripts.cs

@ -0,0 +1,46 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Core.Schemas
{
public static class Scripts
{
public const string Change = "Change";
public const string Create = "Create";
public const string Query = "Query";
public const string Update = "Update";
public const string Delete = "Delete";
public static string GetChange(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Change);
}
public static string GetCreate(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Create);
}
public static string GetQuery(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Query);
}
public static string GetUpdate(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Update);
}
public static string GetDelete(this IReadOnlyDictionary<string, string> scripts)
{
return scripts?.GetOrDefault(Delete);
}
}
}

8
src/Squidex.Domain.Apps.Core.Operations/EventSynchronization/SyncHelpers.cs

@ -6,10 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using NamedIdStatic = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Core.EventSynchronization namespace Squidex.Domain.Apps.Core.EventSynchronization
{ {
@ -30,11 +27,6 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
return lhs.GetType() == rhs.GetType(); return lhs.GetType() == rhs.GetType();
} }
public static NamedId<long> NamedId(this IField field)
{
return NamedIdStatic.Of(field.Id, field.Name);
}
public static bool EqualsJson<T>(this T lhs, T rhs, IJsonSerializer serializer) public static bool EqualsJson<T>(this T lhs, T rhs, IJsonSerializer serializer)
{ {
var lhsJson = serializer.Serialize(lhs); var lhsJson = serializer.Serialize(lhs);

2
src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs

@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var schemaNames = new List<string>(); var schemaNames = new List<string>();
schemaNames.Add(Permission.Any); schemaNames.Add(Permission.Any);
schemaNames.AddRange(schemas.Select(x => x.Name)); schemaNames.AddRange(schemas.Select(x => x.SchemaDef.Name));
return schemaNames; return schemaNames;
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/AssetFieldBuilder.cs

@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class AssetFieldBuilder : FieldBuilder public class AssetFieldBuilder : FieldBuilder
{ {
public AssetFieldBuilder(CreateSchemaField field) public AssetFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/BooleanFieldBuilder.cs

@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class BooleanFieldBuilder : FieldBuilder public class BooleanFieldBuilder : FieldBuilder
{ {
public BooleanFieldBuilder(CreateSchemaField field) public BooleanFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/DateTimeFieldBuilder.cs

@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class DateTimeFieldBuilder : FieldBuilder public class DateTimeFieldBuilder : FieldBuilder
{ {
public DateTimeFieldBuilder(CreateSchemaField field) public DateTimeFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

4
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/FieldBuilder.cs

@ -13,14 +13,14 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public abstract class FieldBuilder public abstract class FieldBuilder
{ {
private readonly CreateSchemaField field; private readonly UpsertSchemaField field;
protected T Properties<T>() where T : FieldProperties protected T Properties<T>() where T : FieldProperties
{ {
return field.Properties as T; return field.Properties as T;
} }
protected FieldBuilder(CreateSchemaField field) protected FieldBuilder(UpsertSchemaField field)
{ {
this.field = field; this.field = field;
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/JsonFieldBuilder.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class JsonFieldBuilder : FieldBuilder public class JsonFieldBuilder : FieldBuilder
{ {
public JsonFieldBuilder(CreateSchemaField field) public JsonFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/NumberFieldBuilder.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class NumberFieldBuilder : FieldBuilder public class NumberFieldBuilder : FieldBuilder
{ {
public NumberFieldBuilder(CreateSchemaField field) public NumberFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

6
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/SchemaBuilder.cs

@ -105,9 +105,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
return this; return this;
} }
private CreateSchemaField AddField<T>(string name) where T : FieldProperties, new() private UpsertSchemaField AddField<T>(string name) where T : FieldProperties, new()
{ {
var field = new CreateSchemaField var field = new UpsertSchemaField
{ {
Name = name.ToCamelCase(), Name = name.ToCamelCase(),
Properties = new T Properties = new T
@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
} }
}; };
command.Fields = command.Fields ?? new List<CreateSchemaField>(); command.Fields = command.Fields ?? new List<UpsertSchemaField>();
command.Fields.Add(field); command.Fields.Add(field);
return field; return field;

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs

@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class StringFieldBuilder : FieldBuilder public class StringFieldBuilder : FieldBuilder
{ {
public StringFieldBuilder(CreateSchemaField field) public StringFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

2
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/TagsFieldBuilder.cs

@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{ {
public class TagsFieldBuilder : FieldBuilder public class TagsFieldBuilder : FieldBuilder
{ {
public TagsFieldBuilder(CreateSchemaField field) public TagsFieldBuilder(UpsertSchemaField field)
: base(field) : base(field)
{ {
} }

6
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateBlogCommandMiddleware.cs

@ -115,8 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
await publish(new ConfigureScripts await publish(new ConfigureScripts
{ {
SchemaId = schemaId.Id, SchemaId = schemaId.Id,
ScriptCreate = Scripts.Slug, Scripts = DefaultScripts.GenerateSlug
ScriptUpdate = Scripts.Slug
}); });
return schemaId; return schemaId;
@ -149,8 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
await publish(new ConfigureScripts await publish(new ConfigureScripts
{ {
SchemaId = schemaId.Id, SchemaId = schemaId.Id,
ScriptCreate = Scripts.Slug, Scripts = DefaultScripts.GenerateSlug
ScriptUpdate = Scripts.Slug
}); });
return schemaId; return schemaId;

15
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateIdentityCommandMiddleware.cs

@ -18,18 +18,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
public sealed class CreateIdentityCommandMiddleware : ICommandMiddleware public sealed class CreateIdentityCommandMiddleware : ICommandMiddleware
{ {
private const string TemplateName = "Identity"; private const string TemplateName = "Identity";
private const string NormalizeScript = @"
var data = ctx.data;
if (data.userName && data.userName.iv) {
data.normalizedUserName = { iv: data.userName.iv.toUpperCase() };
}
if (data.email && data.email.iv) {
data.normalizedEmail = { iv: data.email.iv.toUpperCase() };
}
replace(data);";
public async Task HandleAsync(CommandContext context, Func<Task> next) public async Task HandleAsync(CommandContext context, Func<Task> next)
{ {
@ -250,8 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
await publish(new ConfigureScripts await publish(new ConfigureScripts
{ {
SchemaId = schemaId.Id, SchemaId = schemaId.Id,
ScriptCreate = NormalizeScript, Scripts = DefaultScripts.GenerateUsername
ScriptUpdate = NormalizeScript
}); });
} }

49
src/Squidex.Domain.Apps.Entities/Apps/Templates/DefaultScripts.cs

@ -0,0 +1,49 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Apps.Templates
{
public static class DefaultScripts
{
private const string ScriptToGenerateSlug = @"
var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
replace(data);
}";
private const string ScriptToGenerateUsername = @"
var data = ctx.data;
if (data.userName && data.userName.iv) {
data.normalizedUserName = { iv: data.userName.iv.toUpperCase() };
}
if (data.email && data.email.iv) {
data.normalizedEmail = { iv: data.email.iv.toUpperCase() };
}
replace(data);";
public static readonly Dictionary<string, string> GenerateSlug = new Dictionary<string, string>
{
[Scripts.Create] = ScriptToGenerateSlug,
[Scripts.Update] = ScriptToGenerateSlug
};
public static readonly Dictionary<string, string> GenerateUsername = new Dictionary<string, string>
{
[Scripts.Create] = ScriptToGenerateUsername,
[Scripts.Update] = ScriptToGenerateUsername
};
}
}

2
src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs

@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
var newGuid = GenerateNewGuid(namedId.Id); var newGuid = GenerateNewGuid(namedId.Id);
strings[value] = result = new NamedId<Guid>(newGuid, namedId.Name).ToString(); strings[value] = result = NamedId.Of(newGuid, namedId.Name).ToString();
return true; return true;
} }

2
src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs

@ -301,7 +301,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
if (@event.Payload is AppEvent appEvent && !string.IsNullOrWhiteSpace(CurrentJob.NewAppName)) if (@event.Payload is AppEvent appEvent && !string.IsNullOrWhiteSpace(CurrentJob.NewAppName))
{ {
appEvent.AppId = new NamedId<Guid>(appEvent.AppId.Id, CurrentJob.NewAppName); appEvent.AppId = NamedId.Of(appEvent.AppId.Id, CurrentJob.NewAppName);
} }
foreach (var handler in handlers) foreach (var handler in handlers)

10
src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs

@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
GuardContent.CanCreate(ctx.Schema, c); GuardContent.CanCreate(ctx.Schema, c);
await ctx.ExecuteScriptAndTransformAsync(x => x.ScriptCreate, "Create", c, c.Data); await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Create"), "Create", c, c.Data);
await ctx.EnrichAsync(c.Data); await ctx.EnrichAsync(c.Data);
if (!c.DoNotValidate) if (!c.DoNotValidate)
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (c.Publish) if (c.Publish)
{ {
await ctx.ExecuteScriptAsync(x => x.ScriptChange, "Published", c, c.Data); await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), "Published", c, c.Data);
} }
Create(c); Create(c);
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
reason = StatusChange.Restored; reason = StatusChange.Restored;
} }
await ctx.ExecuteScriptAsync(x => x.ScriptChange, reason, c, Snapshot.Data); await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Change"), reason, c, Snapshot.Data);
ChangeStatus(c, reason); ChangeStatus(c, reason);
} }
@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
GuardContent.CanDelete(ctx.Schema, c); GuardContent.CanDelete(ctx.Schema, c);
await ctx.ExecuteScriptAsync(x => x.ScriptDelete, "Delete", c, Snapshot.Data); await ctx.ExecuteScriptAsync(x => x.SchemaDef.Scripts.GetOrDefault("Delete"), "Delete", c, Snapshot.Data);
Delete(c); Delete(c);
}); });
@ -208,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ctx.ValidateAsync(c.Data); await ctx.ValidateAsync(c.Data);
} }
newData = await ctx.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Update", c, newData, Snapshot.Data); newData = await ctx.ExecuteScriptAndTransformAsync(x => x.SchemaDef.Scripts.GetOrDefault("Update"), "Update", c, newData, Snapshot.Data);
if (isProposal) if (isProposal)
{ {

4
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -158,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var converters = GenerateConverters(context, checkType).ToArray(); var converters = GenerateConverters(context, checkType).ToArray();
var scriptText = schema.ScriptQuery; var scriptText = schema.SchemaDef.Scripts.GetOrDefault("Query");
var isScripting = !string.IsNullOrWhiteSpace(scriptText); var isScripting = !string.IsNullOrWhiteSpace(scriptText);
@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private static void CheckPermission(ISchemaEntity schema, ClaimsPrincipal user) private static void CheckPermission(ISchemaEntity schema, ClaimsPrincipal user)
{ {
var permissions = user.Permissions(); var permissions = user.Permissions();
var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.Name); var permission = Permissions.ForApp(Permissions.AppContentsRead, schema.AppId.Name, schema.SchemaDef.Name);
if (!permissions.Allows(permission)) if (!permissions.Allows(permission))
{ {

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
assetType = new AssetGraphType(this); assetType = new AssetGraphType(this);
assetListType = new ListGraphType(new NonNullGraphType(assetType)); assetListType = new ListGraphType(new NonNullGraphType(assetType));
schemasById = schemas.Where(x => x.IsPublished).ToDictionary(x => x.Id); schemasById = schemas.Where(x => x.SchemaDef.IsPublished).ToDictionary(x => x.Id);
graphQLSchema = BuildSchema(this); graphQLSchema = BuildSchema(this);
graphQLSchema.RegisterValueConverter(JsonConverter.Instance); graphQLSchema.RegisterValueConverter(JsonConverter.Instance);

6
src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
ValidateData(command, e); ValidateData(command, e);
}); });
if (schema.IsSingleton && command.ContentId != schema.Id) if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id)
{ {
throw new DomainException("Singleton content cannot be created."); throw new DomainException("Singleton content cannot be created.");
} }
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
if (schema.IsSingleton && command.Status != Status.Published) if (schema.SchemaDef.IsSingleton && command.Status != Status.Published)
{ {
throw new DomainException("Singleton content archived or unpublished."); throw new DomainException("Singleton content archived or unpublished.");
} }
@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{ {
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
if (schema.IsSingleton) if (schema.SchemaDef.IsSingleton)
{ {
throw new DomainException("Singleton content cannot be deleted."); throw new DomainException("Singleton content cannot be deleted.");
} }

2
src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
context.Command is CreateSchema createSchema && context.Command is CreateSchema createSchema &&
createSchema.Singleton) createSchema.Singleton)
{ {
var schemaId = new NamedId<Guid>(createSchema.SchemaId, createSchema.Name); var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name);
var data = new NamedContentData(); var data = new NamedContentData();

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs

@ -9,10 +9,8 @@ using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class AddField : SchemaCommand public sealed class AddField : ParentFieldCommand
{ {
public long? ParentFieldId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Partitioning { get; set; } public string Partitioning { get; set; }

12
src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs

@ -5,18 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ConfigureScripts : SchemaCommand public sealed class ConfigureScripts : SchemaCommand
{ {
public string ScriptQuery { get; set; } public Dictionary<string, string> Scripts { get; set; }
public string ScriptCreate { get; set; }
public string ScriptUpdate { get; set; }
public string ScriptDelete { get; set; }
public string ScriptChange { get; set; }
} }
} }

14
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs

@ -8,27 +8,25 @@
using System; using System;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.CreateSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class CreateSchema : SchemaCommand, IAppCommand public sealed class CreateSchema : UpsertCommand, IAppCommand
{ {
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
public string Name { get; set; } public string Name { get; set; }
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public bool Singleton { get; set; } public bool Singleton { get; set; }
public bool Publish { get; set; }
public CreateSchema() public CreateSchema()
{ {
SchemaId = Guid.NewGuid(); SchemaId = Guid.NewGuid();
} }
public Schema ToSchema()
{
return ToSchema(Name, Singleton);
}
} }
} }

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs

@ -7,10 +7,8 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public class FieldCommand : SchemaCommand public class FieldCommand : ParentFieldCommand
{ {
public long? ParentFieldId { get; set; }
public long FieldId { get; set; } public long FieldId { get; set; }
} }
} }

14
src/Squidex.Domain.Apps.Entities/Apps/Templates/Scripts.cs → src/Squidex.Domain.Apps.Entities/Schemas/Commands/ParentFieldCommand.cs

@ -5,18 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Entities.Apps.Templates namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public static class Scripts public abstract class ParentFieldCommand : SchemaCommand
{ {
public const string Slug = public long? ParentFieldId { get; set; }
@"var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
replace(data);
}
";
} }
} }

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs

@ -9,10 +9,8 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class ReorderFields : SchemaCommand public sealed class ReorderFields : ParentFieldCommand
{ {
public long? ParentFieldId { get; set; }
public List<long> FieldIds { get; set; } public List<long> FieldIds { get; set; }
} }
} }

13
src/Squidex.Domain.Apps.Entities/Schemas/Commands/SynchronizeSchema.cs

@ -0,0 +1,13 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{
public sealed class SynchronizeSchema : UpsertCommand
{
}
}

101
src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertCommand.cs

@ -0,0 +1,101 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.UpsertSchemaField>;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{
public abstract class UpsertCommand : SchemaCommand
{
public bool Publish { get; set; }
public string Category { get; set; }
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public Dictionary<string, string> Scripts { get; set; }
public Dictionary<string, string> PreviewUrls { get; set; }
public Schema ToSchema(string name, bool isSingleton)
{
var schema = new Schema(name, Properties, isSingleton);
if (Publish)
{
schema = schema.Publish();
}
var totalFields = 0;
if (Fields != null)
{
foreach (var eventField in Fields)
{
totalFields++;
var partitioning = Partitioning.FromString(eventField.Partitioning);
var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning);
if (field is ArrayField arrayField && eventField.Nested?.Count > 0)
{
foreach (var nestedEventField in eventField.Nested)
{
totalFields++;
var nestedField = nestedEventField.Properties.CreateNestedField(totalFields, nestedEventField.Name);
if (nestedEventField.IsHidden)
{
nestedField = nestedField.Hide();
}
if (nestedEventField.IsDisabled)
{
nestedField = nestedField.Disable();
}
if (nestedEventField.IsLocked)
{
nestedField = nestedField.Lock();
}
arrayField = arrayField.AddField(nestedField);
}
field = arrayField;
}
if (eventField.IsHidden)
{
field = field.Hide();
}
if (eventField.IsDisabled)
{
field = field.Disable();
}
if (eventField.IsLocked)
{
field = field.Lock();
}
schema = schema.AddField(field);
}
}
return schema;
}
}
}

4
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs → src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaField.cs

@ -9,10 +9,10 @@ using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class CreateSchemaField : CreateSchemaFieldBase public sealed class UpsertSchemaField : UpsertSchemaFieldBase
{ {
public string Partitioning { get; set; } = "invariant"; public string Partitioning { get; set; } = "invariant";
public List<CreateSchemaNestedField> Nested { get; set; } public List<UpsertSchemaNestedField> Nested { get; set; }
} }
} }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaFieldBase.cs → src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaFieldBase.cs

@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public abstract class CreateSchemaFieldBase public abstract class UpsertSchemaFieldBase
{ {
public string Name { get; set; } public string Name { get; set; }

2
src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaNestedField.cs → src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpsertSchemaNestedField.cs

@ -7,7 +7,7 @@
namespace Squidex.Domain.Apps.Entities.Schemas.Commands namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{ {
public sealed class CreateSchemaNestedField : CreateSchemaFieldBase public sealed class UpsertSchemaNestedField : UpsertSchemaFieldBase
{ {
} }
} }

121
src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs

@ -32,60 +32,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
e("A schema with the same name already exists."); e("A schema with the same name already exists.");
} }
if (command.Fields?.Count > 0) ValidateUpsert(command, e);
{ });
var fieldIndex = 0; }
var fieldPrefix = string.Empty;
foreach (var field in command.Fields)
{
fieldIndex++;
fieldPrefix = $"Fields[{fieldIndex}]";
if (!field.Partitioning.IsValidPartitioning())
{
e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
ValidateField(e, fieldPrefix, field);
if (field.Nested?.Count > 0)
{
if (field.Properties is ArrayFieldProperties)
{
var nestedIndex = 0;
var nestedPrefix = string.Empty;
foreach (var nestedField in field.Nested)
{
nestedIndex++;
nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]";
if (nestedField.Properties is ArrayFieldProperties)
{
e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}");
}
ValidateField(e, nestedPrefix, nestedField);
}
}
else if (field.Nested.Count > 0)
{
e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count) public static void CanSynchronize(SynchronizeSchema command)
{ {
e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested"); Guard.NotNull(command, nameof(command));
}
}
}
if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count) Validate.It(() => "Cannot synchronize schema.", e =>
{ {
e("Fields cannot have duplicate names.", nameof(command.Fields)); ValidateUpsert(command, e);
}
}
}); });
} }
@ -171,7 +128,65 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
Guard.NotNull(command, nameof(command)); Guard.NotNull(command, nameof(command));
} }
private static void ValidateField(AddValidation e, string prefix, CreateSchemaFieldBase field) private static void ValidateUpsert(UpsertCommand command, AddValidation e)
{
if (command.Fields?.Count > 0)
{
var fieldIndex = 0;
var fieldPrefix = string.Empty;
foreach (var field in command.Fields)
{
fieldIndex++;
fieldPrefix = $"Fields[{fieldIndex}]";
if (!field.Partitioning.IsValidPartitioning())
{
e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
ValidateField(field, fieldPrefix, e);
if (field.Nested?.Count > 0)
{
if (field.Properties is ArrayFieldProperties)
{
var nestedIndex = 0;
var nestedPrefix = string.Empty;
foreach (var nestedField in field.Nested)
{
nestedIndex++;
nestedPrefix = $"{fieldPrefix}.Nested[{nestedIndex}]";
if (nestedField.Properties is ArrayFieldProperties)
{
e("Nested field cannot be array fields.", $"{nestedPrefix}.{nameof(nestedField.Properties)}");
}
ValidateField(nestedField, nestedPrefix, e);
}
}
else if (field.Nested.Count > 0)
{
e("Only array fields can have nested fields.", $"{fieldPrefix}.{nameof(field.Partitioning)}");
}
if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count)
{
e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested");
}
}
}
if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count)
{
e("Fields cannot have duplicate names.", nameof(command.Fields));
}
}
}
private static void ValidateField(UpsertSchemaFieldBase field, string prefix, AddValidation e)
{ {
if (!field.Name.IsPropertyName()) if (!field.Name.IsPropertyName())
{ {

21
src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -20,28 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
NamedId<Guid> AppId { get; } NamedId<Guid> AppId { get; }
string Name { get; }
string Category { get; }
bool IsSingleton { get; }
bool IsPublished { get; }
bool IsDeleted { get; } bool IsDeleted { get; }
string ScriptQuery { get; }
string ScriptCreate { get; }
string ScriptUpdate { get; }
string ScriptDelete { get; }
string ScriptChange { get; }
Schema SchemaDef { get; } Schema SchemaDef { get; }
Dictionary<string, string> PreviewUrls { get; }
} }
} }

23
src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs

@ -8,6 +8,7 @@
using System; using System;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using StaticNamedId = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Entities.Schemas namespace Squidex.Domain.Apps.Entities.Schemas
{ {
@ -15,7 +16,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
public static NamedId<Guid> NamedId(this ISchemaEntity schema) public static NamedId<Guid> NamedId(this ISchemaEntity schema)
{ {
return new NamedId<Guid>(schema.Id, schema.Name); return StaticNamedId.Of(schema.Id, schema.SchemaDef.Name);
}
public static long MaxId(this Schema schema)
{
var id = 0L;
foreach (var field in schema.Fields)
{
if (field is IArrayField arrayField)
{
foreach (var nestedField in arrayField.Fields)
{
id = Math.Max(id, nestedField.Id);
}
}
id = Math.Max(id, field.Id);
}
return id;
} }
public static string EscapePartition(this string value) public static string EscapePartition(this string value)

129
src/Squidex.Domain.Apps.Entities/Schemas/SchemaGrain.cs

@ -6,8 +6,8 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.EventSynchronization;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.Schemas.Guards; using Squidex.Domain.Apps.Entities.Schemas.Guards;
@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -27,17 +28,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public sealed class SchemaGrain : SquidexDomainObjectGrain<SchemaState>, ISchemaGrain public sealed class SchemaGrain : SquidexDomainObjectGrain<SchemaState>, ISchemaGrain
{ {
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly FieldRegistry registry; private readonly IJsonSerializer serializer;
public SchemaGrain(IStore<Guid> store, ISemanticLog log, IAppProvider appProvider, FieldRegistry registry) public SchemaGrain(IStore<Guid> store, ISemanticLog log, IAppProvider appProvider, IJsonSerializer serializer)
: base(store, log) : base(store, log)
{ {
Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(registry, nameof(registry)); Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider; this.appProvider = appProvider;
this.registry = registry; this.serializer = serializer;
} }
protected override Task<object> ExecuteAsync(IAggregateCommand command) protected override Task<object> ExecuteAsync(IAggregateCommand command)
@ -200,45 +201,32 @@ namespace Squidex.Domain.Apps.Entities.Schemas
} }
} }
public void Create(CreateSchema command) public void Synchronize(SynchronizeSchema command)
{ {
var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = NamedId.Of(command.SchemaId, command.Name) }); var schemaSource = Snapshot.SchemaDef;
var schemaTarget = command.ToSchema(schemaSource.Name, schemaSource.IsSingleton);
if (command.Fields != null)
{
@event.Fields = new List<SchemaCreatedField>();
foreach (var commandField in command.Fields)
{
var eventField = SimpleMapper.Map(commandField, new SchemaCreatedField());
@event.Fields.Add(eventField);
if (commandField.Nested != null) var @events = schemaTarget.Synchronize(schemaSource, serializer, () => Snapshot.SchemaFieldsTotal + 1);
{
eventField.Nested = new List<SchemaCreatedNestedField>();
foreach (var nestedField in commandField.Nested)
{
var eventNestedField = SimpleMapper.Map(nestedField, new SchemaCreatedNestedField());
eventField.Nested.Add(eventNestedField); foreach (var @event in @events)
} {
} RaiseEvent(@event);
}
} }
}
RaiseEvent(@event); public void Create(CreateSchema command)
{
RaiseEvent(command, new SchemaCreated { SchemaId = NamedId.Of(command.SchemaId, command.Name), Schema = command.ToSchema() });
} }
public void Add(AddField command) public void Add(AddField command)
{ {
RaiseEvent(SimpleMapper.Map(command, new FieldAdded { ParentFieldId = GetFieldId(command.ParentFieldId), FieldId = CreateFieldId(command) })); RaiseEvent(command, new FieldAdded { FieldId = NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name) });
} }
public void UpdateField(UpdateField command) public void UpdateField(UpdateField command)
{ {
RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated())); RaiseEvent(command, new FieldUpdated());
} }
public void LockField(LockField command) public void LockField(LockField command)
@ -273,88 +261,86 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public void Reorder(ReorderFields command) public void Reorder(ReorderFields command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered { ParentFieldId = GetFieldId(command.ParentFieldId) })); RaiseEvent(command, new SchemaFieldsReordered());
} }
public void Publish(PublishSchema command) public void Publish(PublishSchema command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaPublished())); RaiseEvent(command, new SchemaPublished());
} }
public void Unpublish(UnpublishSchema command) public void Unpublish(UnpublishSchema command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished())); RaiseEvent(command, new SchemaUnpublished());
} }
public void ConfigureScripts(ConfigureScripts command) public void ConfigureScripts(ConfigureScripts command)
{ {
RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured())); RaiseEvent(command, new SchemaScriptsConfigured());
} }
public void ChangeCategory(ChangeCategory command) public void ChangeCategory(ChangeCategory command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaCategoryChanged())); RaiseEvent(command, new SchemaCategoryChanged());
} }
public void ConfigurePreviewUrls(ConfigurePreviewUrls command) public void ConfigurePreviewUrls(ConfigurePreviewUrls command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaPreviewUrlsConfigured())); RaiseEvent(command, new SchemaPreviewUrlsConfigured());
} }
public void Delete(DeleteSchema command) public void Update(UpdateSchema command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaDeleted())); RaiseEvent(command, new SchemaUpdated());
} }
public void Update(UpdateSchema command) public void Delete(DeleteSchema command)
{ {
RaiseEvent(SimpleMapper.Map(command, new SchemaUpdated())); RaiseEvent(command, new SchemaDeleted());
} }
private void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) private void RaiseEvent<TCommand, TEvent>(TCommand command, TEvent @event) where TCommand : SchemaCommand where TEvent : SchemaEvent
{ {
SimpleMapper.Map(fieldCommand, @event); SimpleMapper.Map(command, @event);
if (fieldCommand.ParentFieldId.HasValue) NamedId<long> GetFieldId(long? id)
{ {
if (Snapshot.SchemaDef.FieldsById.TryGetValue(fieldCommand.ParentFieldId.Value, out var field)) if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field))
{ {
@event.ParentFieldId = NamedId.Of(field.Id, field.Name); return NamedId.Of(field.Id, field.Name);
}
return null;
}
if (field is IArrayField arrayField && arrayField.FieldsById.TryGetValue(fieldCommand.FieldId, out var nestedField)) if (command is ParentFieldCommand pc && @event is ParentFieldEvent pe)
{
if (pc.ParentFieldId.HasValue)
{
if (Snapshot.SchemaDef.FieldsById.TryGetValue(pc.ParentFieldId.Value, out var field))
{ {
@event.FieldId = NamedId.Of(nestedField.Id, nestedField.Name); pe.ParentFieldId = NamedId.Of(field.Id, field.Name);
if (field is IArrayField arrayField && command is FieldCommand fc && @event is FieldEvent fe && arrayField.FieldsById.TryGetValue(fc.FieldId, out var nestedField))
{
fe.FieldId = NamedId.Of(nestedField.Id, nestedField.Name);
}
} }
} }
} else if (command is FieldCommand fc && @event is FieldEvent fe)
else {
{ fe.FieldId = GetFieldId(fc.FieldId);
@event.FieldId = GetFieldId(fieldCommand.FieldId); }
} }
RaiseEvent(@event); RaiseEvent(@event);
} }
private NamedId<long> CreateFieldId(AddField command)
{
return NamedId.Of(Snapshot.TotalFields + 1L, command.Name);
}
private NamedId<long> GetFieldId(long? id)
{
if (id.HasValue && Snapshot.SchemaDef.FieldsById.TryGetValue(id.Value, out var field))
{
return NamedId.Of(field.Id, field.Name);
}
return null;
}
private void RaiseEvent(SchemaEvent @event) private void RaiseEvent(SchemaEvent @event)
{ {
if (@event.SchemaId == null) if (@event.SchemaId == null)
{ {
@event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.Name); @event.SchemaId = NamedId.Of(Snapshot.Id, Snapshot.SchemaDef.Name);
} }
if (@event.AppId == null) if (@event.AppId == null)
@ -365,6 +351,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas
RaiseEvent(Envelope.Create(@event)); RaiseEvent(Envelope.Create(@event));
} }
private NamedId<long> CreateFieldId(AddField command)
{
return NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name);
}
private void VerifyNotDeleted() private void VerifyNotDeleted()
{ {
if (Snapshot.IsDeleted) if (Snapshot.IsDeleted)
@ -375,7 +366,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
protected override SchemaState OnEvent(Envelope<IEvent> @event) protected override SchemaState OnEvent(Envelope<IEvent> @event)
{ {
return Snapshot.Apply(@event, registry); return Snapshot.Apply(@event);
} }
public Task<J<ISchemaEntity>> GetStateAsync() public Task<J<ISchemaEntity>> GetStateAsync()

165
src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -15,7 +14,6 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas.State namespace Squidex.Domain.Apps.Entities.Schemas.State
@ -27,128 +25,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
[DataMember] [DataMember]
public string Name { get; set; } public long SchemaFieldsTotal { get; set; }
[DataMember]
public string Category { get; set; }
[DataMember]
public int TotalFields { get; set; }
[DataMember] [DataMember]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[DataMember]
public bool IsSingleton { get; set; }
[DataMember]
public string ScriptQuery { get; set; }
[DataMember]
public string ScriptCreate { get; set; }
[DataMember]
public string ScriptUpdate { get; set; }
[DataMember]
public string ScriptDelete { get; set; }
[DataMember]
public string ScriptChange { get; set; }
[DataMember]
public Dictionary<string, string> PreviewUrls { get; set; }
[DataMember] [DataMember]
public Schema SchemaDef { get; set; } public Schema SchemaDef { get; set; }
[IgnoreDataMember] protected void On(SchemaCreated @event)
public bool IsPublished
{ {
get { return SchemaDef.IsPublished; } SchemaDef = @event.Schema;
} SchemaFieldsTotal = @event.Schema.MaxId();
protected void On(SchemaCreated @event, FieldRegistry registry)
{
Name = @event.Name;
IsSingleton = @event.Singleton;
var schema = new Schema(@event.Name);
if (@event.Properties != null)
{
schema = schema.Update(@event.Properties);
}
if (@event.Publish)
{
schema = schema.Publish();
}
if (@event.Fields != null)
{
foreach (var eventField in @event.Fields)
{
TotalFields++;
var partitioning = Partitioning.FromString(eventField.Partitioning);
var field = registry.CreateRootField(TotalFields, eventField.Name, partitioning, eventField.Properties);
if (field is ArrayField arrayField && eventField.Nested?.Count > 0)
{
foreach (var nestedEventField in eventField.Nested)
{
TotalFields++;
var nestedField = registry.CreateNestedField(TotalFields, nestedEventField.Name, nestedEventField.Properties);
if (nestedEventField.IsHidden)
{
nestedField = nestedField.Hide();
}
if (nestedEventField.IsDisabled)
{
nestedField = nestedField.Disable();
}
arrayField = arrayField.AddField(nestedField);
}
field = arrayField;
}
if (eventField.IsHidden)
{
field = field.Hide();
}
if (eventField.IsDisabled)
{
field = field.Disable();
}
if (eventField.IsLocked)
{
field = field.Lock();
}
schema = schema.AddField(field);
}
}
SchemaDef = schema;
AppId = @event.AppId; AppId = @event.AppId;
} }
protected void On(FieldAdded @event, FieldRegistry registry) protected void On(FieldAdded @event)
{ {
if (@event.ParentFieldId != null) if (@event.ParentFieldId != null)
{ {
var field = registry.CreateNestedField(@event.FieldId.Id, @event.Name, @event.Properties); var field = @event.Properties.CreateNestedField(@event.FieldId.Id, @event.Name);
SchemaDef = SchemaDef.UpdateField(@event.ParentFieldId.Id, x => ((ArrayField)x).AddField(field)); SchemaDef = SchemaDef.UpdateField(@event.ParentFieldId.Id, x => ((ArrayField)x).AddField(field));
} }
@ -156,95 +53,95 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
{ {
var partitioning = Partitioning.FromString(@event.Partitioning); var partitioning = Partitioning.FromString(@event.Partitioning);
var field = registry.CreateRootField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties); var field = @event.Properties.CreateRootField(@event.FieldId.Id, @event.Name, partitioning);
SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id); SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id);
SchemaDef = SchemaDef.AddField(field); SchemaDef = SchemaDef.AddField(field);
} }
TotalFields++; SchemaFieldsTotal++;
} }
protected void On(SchemaCategoryChanged @event, FieldRegistry registry) protected void On(SchemaCategoryChanged @event)
{ {
Category = @event.Name; SchemaDef = SchemaDef.ChangeCategory(@event.Name);
} }
protected void On(SchemaPreviewUrlsConfigured @event, FieldRegistry registry) protected void On(SchemaPreviewUrlsConfigured @event)
{ {
PreviewUrls = @event.PreviewUrls; SchemaDef = SchemaDef.ConfigurePreviewUrls(@event.PreviewUrls);
} }
protected void On(SchemaPublished @event, FieldRegistry registry) protected void On(SchemaScriptsConfigured @event)
{
SchemaDef = SchemaDef.ConfigureScripts(@event.Scripts);
}
protected void On(SchemaPublished @event)
{ {
SchemaDef = SchemaDef.Publish(); SchemaDef = SchemaDef.Publish();
} }
protected void On(SchemaUnpublished @event, FieldRegistry registry) protected void On(SchemaUnpublished @event)
{ {
SchemaDef = SchemaDef.Unpublish(); SchemaDef = SchemaDef.Unpublish();
} }
protected void On(SchemaUpdated @event, FieldRegistry registry) protected void On(SchemaUpdated @event)
{ {
SchemaDef = SchemaDef.Update(@event.Properties); SchemaDef = SchemaDef.Update(@event.Properties);
} }
protected void On(SchemaFieldsReordered @event, FieldRegistry registry) protected void On(SchemaFieldsReordered @event)
{ {
SchemaDef = SchemaDef.ReorderFields(@event.FieldIds, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.ReorderFields(@event.FieldIds, @event.ParentFieldId?.Id);
} }
protected void On(FieldUpdated @event, FieldRegistry registry) protected void On(FieldUpdated @event)
{ {
SchemaDef = SchemaDef.UpdateField(@event.FieldId.Id, @event.Properties, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.UpdateField(@event.FieldId.Id, @event.Properties, @event.ParentFieldId?.Id);
} }
protected void On(FieldLocked @event, FieldRegistry registry) protected void On(FieldLocked @event)
{ {
SchemaDef = SchemaDef.LockField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.LockField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(FieldDisabled @event, FieldRegistry registry) protected void On(FieldDisabled @event)
{ {
SchemaDef = SchemaDef.DisableField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.DisableField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(FieldEnabled @event, FieldRegistry registry) protected void On(FieldEnabled @event)
{ {
SchemaDef = SchemaDef.EnableField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.EnableField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(FieldHidden @event, FieldRegistry registry) protected void On(FieldHidden @event)
{ {
SchemaDef = SchemaDef.HideField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.HideField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(FieldShown @event, FieldRegistry registry) protected void On(FieldShown @event)
{ {
SchemaDef = SchemaDef.ShowField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.ShowField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(FieldDeleted @event, FieldRegistry registry) protected void On(FieldDeleted @event)
{ {
SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id, @event.ParentFieldId?.Id); SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id, @event.ParentFieldId?.Id);
} }
protected void On(SchemaDeleted @event, FieldRegistry registry) protected void On(SchemaDeleted @event)
{ {
IsDeleted = true; IsDeleted = true;
} }
protected void On(ScriptsConfigured @event, FieldRegistry registry) public SchemaState Apply(Envelope<IEvent> @event)
{
SimpleMapper.Map(@event, this);
}
public SchemaState Apply(Envelope<IEvent> @event, FieldRegistry registry)
{ {
var payload = (SquidexEvent)@event.Payload; var payload = (SquidexEvent)@event.Payload;
return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload, registry)); return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload));
} }
} }
} }

4
src/Squidex.Domain.Apps.Events/Schemas/FieldEvent.cs

@ -9,10 +9,8 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
{ {
public abstract class FieldEvent : SchemaEvent public abstract class FieldEvent : ParentFieldEvent
{ {
public NamedId<long> FieldId { get; set; } public NamedId<long> FieldId { get; set; }
public NamedId<long> ParentFieldId { get; set; }
} }
} }

15
src/Squidex.Domain.Apps.Events/Schemas/ScriptsConfigured.cs → src/Squidex.Domain.Apps.Events/Schemas/ParentFieldEvent.cs

@ -5,21 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
{ {
[EventType(nameof(ScriptsConfigured))] public abstract class ParentFieldEvent : SchemaEvent
public sealed class ScriptsConfigured : SchemaEvent
{ {
public string ScriptQuery { get; set; } public NamedId<long> ParentFieldId { get; set; }
public string ScriptCreate { get; set; }
public string ScriptUpdate { get; set; }
public string ScriptDelete { get; set; }
public string ScriptChange { get; set; }
} }
} }

15
src/Squidex.Domain.Apps.Events/Schemas/SchemaCreated.cs

@ -1,4 +1,4 @@
// ========================================================================== // ==========================================================================
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
@ -7,21 +7,12 @@
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Events.Schemas.SchemaCreatedField>;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
{ {
[EventType(nameof(SchemaCreated))] [EventType(nameof(SchemaCreated), 2)]
public sealed class SchemaCreated : SchemaEvent public sealed class SchemaCreated : SchemaEvent
{ {
public string Name { get; set; } public Schema Schema { get; set; }
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public bool Singleton { get; set; }
public bool Publish { get; set; }
} }
} }

5
src/Squidex.Domain.Apps.Events/Schemas/SchemaFieldsReordered.cs

@ -6,16 +6,13 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Schemas namespace Squidex.Domain.Apps.Events.Schemas
{ {
[EventType(nameof(SchemaFieldsReordered))] [EventType(nameof(SchemaFieldsReordered))]
public sealed class SchemaFieldsReordered : SchemaEvent public sealed class SchemaFieldsReordered : ParentFieldEvent
{ {
public NamedId<long> ParentFieldId { get; set; }
public List<long> FieldIds { get; set; } public List<long> FieldIds { get; set; }
} }
} }

2
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs

@ -77,7 +77,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
var appBasePath = $"/content/{app.Name}"; var appBasePath = $"/content/{app.Name}";
foreach (var schema in schemas.Where(x => x.IsPublished).Select(x => x.SchemaDef)) foreach (var schema in schemas.Select(x => x.SchemaDef).Where(x => x.IsPublished))
{ {
new SchemaSwaggerGenerator(document, app.Name, appBasePath, schema, AppendSchema, app.PartitionResolver()).GenerateSchemaOperations(); new SchemaSwaggerGenerator(document, app.Name, appBasePath, schema, AppendSchema, app.PartitionResolver()).GenerateSchemaOperations();
} }

8
src/Squidex/Areas/Api/Controllers/Schemas/Models/CreateSchemaDto.cs

@ -57,21 +57,21 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
if (Fields != null) if (Fields != null)
{ {
command.Fields = new List<CreateSchemaField>(); command.Fields = new List<UpsertSchemaField>();
foreach (var fieldDto in Fields) foreach (var fieldDto in Fields)
{ {
var rootProperties = fieldDto?.Properties.ToProperties(); var rootProperties = fieldDto?.Properties.ToProperties();
var rootField = SimpleMapper.Map(fieldDto, new CreateSchemaField { Properties = rootProperties }); var rootField = SimpleMapper.Map(fieldDto, new UpsertSchemaField { Properties = rootProperties });
if (fieldDto.Nested != null) if (fieldDto.Nested != null)
{ {
rootField.Nested = new List<CreateSchemaNestedField>(); rootField.Nested = new List<UpsertSchemaNestedField>();
foreach (var nestedFieldDto in fieldDto.Nested) foreach (var nestedFieldDto in fieldDto.Nested)
{ {
var nestedProperties = nestedFieldDto?.Properties.ToProperties(); var nestedProperties = nestedFieldDto?.Properties.ToProperties();
var nestedField = SimpleMapper.Map(nestedFieldDto, new CreateSchemaNestedField { Properties = nestedProperties }); var nestedField = SimpleMapper.Map(nestedFieldDto, new UpsertSchemaNestedField { Properties = nestedProperties });
rootField.Nested.Add(nestedField); rootField.Nested.Add(nestedField);
} }

4
src/Squidex/Config/Domain/SerializationServices.cs

@ -30,7 +30,6 @@ namespace Squidex.Config.Domain
.MapUnmapped(SquidexEvents.Assembly) .MapUnmapped(SquidexEvents.Assembly)
.MapUnmapped(SquidexInfrastructure.Assembly) .MapUnmapped(SquidexInfrastructure.Assembly)
.MapUnmapped(SquidexMigrations.Assembly); .MapUnmapped(SquidexMigrations.Assembly);
private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry);
public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings(); public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings();
public static readonly JsonSerializer DefaultJsonSerializer; public static readonly JsonSerializer DefaultJsonSerializer;
@ -70,6 +69,8 @@ namespace Squidex.Config.Domain
static SerializationServices() static SerializationServices()
{ {
FieldRegistry.Setup(TypeNameRegistry);
ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto); ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto);
DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings); DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings);
@ -77,7 +78,6 @@ namespace Squidex.Config.Domain
public static IServiceCollection AddMySerializers(this IServiceCollection services) public static IServiceCollection AddMySerializers(this IServiceCollection services)
{ {
services.AddSingleton(FieldRegistry);
services.AddSingleton(DefaultJsonSettings); services.AddSingleton(DefaultJsonSettings);
services.AddSingleton(DefaultJsonSerializer); services.AddSingleton(DefaultJsonSerializer);
services.AddSingleton(TypeNameRegistry); services.AddSingleton(TypeNameRegistry);

2
src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs

@ -97,7 +97,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
throw new DomainObjectNotFoundException(schemaName, typeof(ISchemaEntity)); throw new DomainObjectNotFoundException(schemaName, typeof(ISchemaEntity));
} }
return NamedId.Of(schema.Id, schema.Name); return schema.NamedId();
} }
} }

2
src/Squidex/Pipeline/UrlGenerator.cs

@ -57,7 +57,7 @@ namespace Squidex.Pipeline
public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content) public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content)
{ {
return urlsOptions.BuildUrl($"api/content/{app.Name}/{schema.Name}/{content.Id}"); return urlsOptions.BuildUrl($"api/content/{app.Name}/{schema.SchemaDef.Name}/{content.Id}");
} }
public string GenerateContentUIUrl(NamedId<Guid> appId, NamedId<Guid> schemaId, Guid contentId) public string GenerateContentUIUrl(NamedId<Guid> appId, NamedId<Guid> schemaId, Guid contentId)

67
tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs

@ -1,67 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Xunit;
namespace Squidex.Domain.Apps.Core.Model.Schemas
{
public class FieldRegistryTests
{
private readonly FieldRegistry sut = new FieldRegistry(new TypeNameRegistry());
private sealed class InvalidProperties : FieldProperties
{
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
return default;
}
public override T Accept<T>(IFieldVisitor<T> visitor, IField field)
{
return default;
}
public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null)
{
return null;
}
public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null)
{
return null;
}
}
[Fact]
public void Should_throw_exception_if_creating_field_and_field_is_not_registered()
{
Assert.Throws<InvalidOperationException>(() => sut.CreateRootField(1, "name", Partitioning.Invariant, new InvalidProperties()));
}
[Theory]
[InlineData(typeof(AssetsFieldProperties))]
[InlineData(typeof(BooleanFieldProperties))]
[InlineData(typeof(DateTimeFieldProperties))]
[InlineData(typeof(GeolocationFieldProperties))]
[InlineData(typeof(JsonFieldProperties))]
[InlineData(typeof(NumberFieldProperties))]
[InlineData(typeof(ReferencesFieldProperties))]
[InlineData(typeof(StringFieldProperties))]
[InlineData(typeof(TagsFieldProperties))]
public void Should_create_field_by_properties(Type propertyType)
{
var properties = (FieldProperties)Activator.CreateInstance(propertyType);
var field = sut.CreateRootField(1, "name", Partitioning.Invariant, properties);
Assert.Equal(properties, field.RawProperties);
}
}
}

2
tests/Squidex.Domain.Apps.Core.Tests/Operations/EventSynchronization/SchemaSynchronizerTests.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EventSynchronization
public void Should_create_events_if_category_changed() public void Should_create_events_if_category_changed()
{ {
var sourceSchema = new Schema("source"); var sourceSchema = new Schema("source");
var targetSchema = new Schema("target").MoveTo("Category"); var targetSchema = new Schema("target").ChangeCategory("Category");
var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator); var events = sourceSchema.Synchronize(targetSchema, jsonSerializer, idGenerator);

69
tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsByNameIndexGrainTests.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Xunit; using Xunit;
@ -19,10 +20,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{ {
private readonly IStore<string> store = A.Fake<IStore<string>>(); private readonly IStore<string> store = A.Fake<IStore<string>>();
private readonly IPersistence<AppsByNameIndexGrain.GrainState> persistence = A.Fake<IPersistence<AppsByNameIndexGrain.GrainState>>(); private readonly IPersistence<AppsByNameIndexGrain.GrainState> persistence = A.Fake<IPersistence<AppsByNameIndexGrain.GrainState>>();
private readonly Guid appId1 = Guid.NewGuid(); private readonly NamedId<Guid> appId1 = NamedId.Of(Guid.NewGuid(), "my-app1");
private readonly Guid appId2 = Guid.NewGuid(); private readonly NamedId<Guid> appId2 = NamedId.Of(Guid.NewGuid(), "my-app2");
private readonly string appName1 = "my-app1";
private readonly string appName2 = "my-app2";
private readonly AppsByNameIndexGrain sut; private readonly AppsByNameIndexGrain sut;
public AppsByNameIndexGrainTests() public AppsByNameIndexGrainTests()
@ -37,11 +36,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
[Fact] [Fact]
public async Task Should_add_app_id_to_index() public async Task Should_add_app_id_to_index()
{ {
await sut.AddAppAsync(appId1, appName1); await sut.AddAppAsync(appId1.Id, appId1.Name);
var result = await sut.GetAppIdAsync(appName1); var result = await sut.GetAppIdAsync(appId1.Name);
Assert.Equal(appId1, result); Assert.Equal(appId1.Id, result);
A.CallTo(() => persistence.WriteSnapshotAsync(A<AppsByNameIndexGrain.GrainState>.Ignored)) A.CallTo(() => persistence.WriteSnapshotAsync(A<AppsByNameIndexGrain.GrainState>.Ignored))
.MustHaveHappened(); .MustHaveHappened();
@ -50,79 +49,79 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
[Fact] [Fact]
public async Task Should_not_be_able_to_reserve_index_if_name_taken() public async Task Should_not_be_able_to_reserve_index_if_name_taken()
{ {
await sut.AddAppAsync(appId2, appName1); await sut.AddAppAsync(appId2.Id, appId1.Name);
Assert.False(await sut.ReserveAppAsync(appId1, appName1)); Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name));
} }
[Fact] [Fact]
public async Task Should_not_be_able_to_reserve_if_name_reserved() public async Task Should_not_be_able_to_reserve_if_name_reserved()
{ {
await sut.ReserveAppAsync(appId2, appName1); await sut.ReserveAppAsync(appId2.Id, appId1.Name);
Assert.False(await sut.ReserveAppAsync(appId1, appName1)); Assert.False(await sut.ReserveAppAsync(appId1.Id, appId1.Name));
} }
[Fact] [Fact]
public async Task Should_not_be_able_to_reserve_if_id_taken() public async Task Should_not_be_able_to_reserve_if_id_taken()
{ {
await sut.AddAppAsync(appId1, appName1); await sut.AddAppAsync(appId1.Id, appId1.Name);
Assert.False(await sut.ReserveAppAsync(appId1, appName2)); Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name));
} }
[Fact] [Fact]
public async Task Should_not_be_able_to_reserve_if_id_reserved() public async Task Should_not_be_able_to_reserve_if_id_reserved()
{ {
await sut.ReserveAppAsync(appId1, appName1); await sut.ReserveAppAsync(appId1.Id, appId1.Name);
Assert.False(await sut.ReserveAppAsync(appId1, appName2)); Assert.False(await sut.ReserveAppAsync(appId1.Id, appId2.Name));
} }
[Fact] [Fact]
public async Task Should_be_able_to_reserve_if_id_and_name_not_reserved() public async Task Should_be_able_to_reserve_if_id_and_name_not_reserved()
{ {
await sut.ReserveAppAsync(appId1, appName1); await sut.ReserveAppAsync(appId1.Id, appId1.Name);
Assert.True(await sut.ReserveAppAsync(appId2, appName2)); Assert.True(await sut.ReserveAppAsync(appId2.Id, appId2.Name));
} }
[Fact] [Fact]
public async Task Should_be_able_to_reserve_after_app_removed() public async Task Should_be_able_to_reserve_after_app_removed()
{ {
await sut.AddAppAsync(appId1, appName1); await sut.AddAppAsync(appId1.Id, appId1.Name);
await sut.RemoveAppAsync(appId1); await sut.RemoveAppAsync(appId1.Id);
Assert.True(await sut.ReserveAppAsync(appId1, appName1)); Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name));
} }
[Fact] [Fact]
public async Task Should_be_able_to_reserve_after_reservation_removed() public async Task Should_be_able_to_reserve_after_reservation_removed()
{ {
await sut.ReserveAppAsync(appId1, appName1); await sut.ReserveAppAsync(appId1.Id, appId1.Name);
await sut.RemoveReservationAsync(appId1, appName1); await sut.RemoveReservationAsync(appId1.Id, appId1.Name);
Assert.True(await sut.ReserveAppAsync(appId1, appName1)); Assert.True(await sut.ReserveAppAsync(appId1.Id, appId1.Name));
} }
[Fact] [Fact]
public async Task Should_return_many_app_ids() public async Task Should_return_many_app_ids()
{ {
await sut.AddAppAsync(appId1, appName1); await sut.AddAppAsync(appId1.Id, appId1.Name);
await sut.AddAppAsync(appId2, appName2); await sut.AddAppAsync(appId2.Id, appId2.Name);
var ids = await sut.GetAppIdsAsync(appName1, appName2); var ids = await sut.GetAppIdsAsync(appId1.Name, appId2.Name);
Assert.Equal(new List<Guid> { appId1, appId2 }, ids); Assert.Equal(new List<Guid> { appId1.Id, appId2.Id }, ids);
} }
[Fact] [Fact]
public async Task Should_remove_app_id_from_index() public async Task Should_remove_app_id_from_index()
{ {
await sut.AddAppAsync(appId1, appName1); await sut.AddAppAsync(appId1.Id, appId1.Name);
await sut.RemoveAppAsync(appId1); await sut.RemoveAppAsync(appId1.Id);
var result = await sut.GetAppIdAsync(appName1); var result = await sut.GetAppIdAsync(appId1.Name);
Assert.Equal(Guid.Empty, result); Assert.Equal(Guid.Empty, result);
@ -135,16 +134,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
{ {
var state = new Dictionary<string, Guid> var state = new Dictionary<string, Guid>
{ {
[appName1] = appId1, [appId1.Name] = appId1.Id,
[appName2] = appId2 [appId2.Name] = appId2.Id
}; };
await sut.RebuildAsync(state); await sut.RebuildAsync(state);
Assert.Equal(appId1, await sut.GetAppIdAsync(appName1)); Assert.Equal(appId1.Id, await sut.GetAppIdAsync(appId1.Name));
Assert.Equal(appId2, await sut.GetAppIdAsync(appName2)); Assert.Equal(appId2.Id, await sut.GetAppIdAsync(appId2.Name));
Assert.Equal(new List<Guid> { appId1, appId2 }, await sut.GetAppIdsAsync()); Assert.Equal(new List<Guid> { appId1.Id, appId2.Id }, await sut.GetAppIdsAsync());
Assert.Equal(2, await sut.CountAsync()); Assert.Equal(2, await sut.CountAsync());

3
tests/Squidex.Domain.Apps.Entities.Tests/Apps/RolePermissionsProviderTests.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Xunit; using Xunit;
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
var schema = A.Fake<ISchemaEntity>(); var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.Name).Returns(name); A.CallTo(() => schema.SchemaDef).Returns(new Schema(name));
return schema; return schema;
} }

19
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs

@ -26,8 +26,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly ITagService tagService = A.Fake<ITagService>(); private readonly ITagService tagService = A.Fake<ITagService>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAppEntity app = A.Fake<IAppEntity>(); private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly Guid appId = Guid.NewGuid(); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly string appName = "my-app";
private readonly ClaimsIdentity identity = new ClaimsIdentity(); private readonly ClaimsIdentity identity = new ClaimsIdentity();
private readonly QueryContext context; private readonly QueryContext context;
private readonly AssetQueryService sut; private readonly AssetQueryService sut;
@ -36,13 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
var user = new ClaimsPrincipal(identity); var user = new ClaimsPrincipal(identity);
A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
context = QueryContext.Create(app, user); context = QueryContext.Create(app, user);
A.CallTo(() => tagService.DenormalizeTagsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2", "id3"))) A.CallTo(() => tagService.DenormalizeTagsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.IsSameSequenceAs("id1", "id2", "id3")))
.Returns(new Dictionary<string, string> .Returns(new Dictionary<string, string>
{ {
["id1"] = "name1", ["id1"] = "name1",
@ -74,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var ids = HashSet.Of(id1, id2); var ids = HashSet.Of(id1, id2);
A.CallTo(() => assetRepository.QueryAsync(appId, A<HashSet<Guid>>.That.IsSameSequenceAs(ids))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<HashSet<Guid>>.That.IsSameSequenceAs(ids)))
.Returns(ResultList.Create(8, .Returns(ResultList.Create(8,
CreateAsset(id1, "id1", "id2", "id3"), CreateAsset(id1, "id1", "id2", "id3"),
CreateAsset(id2))); CreateAsset(id2)));
@ -91,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
[Fact] [Fact]
public async Task Should_load_assets_with_query_and_resolve_tags() public async Task Should_load_assets_with_query_and_resolve_tags()
{ {
A.CallTo(() => assetRepository.QueryAsync(appId, A<Query>.Ignored)) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.Ignored))
.Returns(ResultList.Create(8, .Returns(ResultList.Create(8,
CreateAsset(Guid.NewGuid(), "id1", "id2"), CreateAsset(Guid.NewGuid(), "id1", "id2"),
CreateAsset(Guid.NewGuid(), "id2", "id3"))); CreateAsset(Guid.NewGuid(), "id2", "id3")));
@ -110,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World")); await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=100&$orderby=fileName asc&$search=Hello World"));
A.CallTo(() => assetRepository.QueryAsync(appId, A<Query>.That.Matches(x => x.ToString() == "FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Matches(x => x.ToString() == "FullText: 'Hello World'; Take: 100; Sort: fileName Ascending")))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -119,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
await sut.QueryAsync(context, Q.Empty.WithODataQuery("$filter=fileName eq 'ABC'")); await sut.QueryAsync(context, Q.Empty.WithODataQuery("$filter=fileName eq 'ABC'"));
A.CallTo(() => assetRepository.QueryAsync(appId, A<Query>.That.Matches(x => x.ToString() == "Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Matches(x => x.ToString() == "Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -128,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=300&$skip=20")); await sut.QueryAsync(context, Q.Empty.WithODataQuery("$top=300&$skip=20"));
A.CallTo(() => assetRepository.QueryAsync(appId, A<Query>.That.Matches(x => x.ToString() == "Skip: 20; Take: 200; Sort: lastModified Descending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Matches(x => x.ToString() == "Skip: 20; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened(); .MustHaveHappened();
} }

2
tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs

@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
var @event = new MyEvent var @event = new MyEvent
{ {
GuidNamed = new NamedId<Guid>(RandomGuid(), $"name{i}"), GuidNamed = NamedId.Of(RandomGuid(), $"name{i}"),
GuidRaw = RandomGuid(), GuidRaw = RandomGuid(),
Values = new Dictionary<Guid, string> Values = new Dictionary<Guid, string>
{ {

16
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using NodaTime; using NodaTime;
@ -74,12 +75,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentGrainTests() public ContentGrainTests()
{ {
var scripts = new Dictionary<string, string>
{
[Scripts.Change] = "<change-script>",
[Scripts.Create] = "<create-script>",
[Scripts.Delete] = "<delete-script>",
[Scripts.Update] = "<update-script>",
};
var schemaDef = var schemaDef =
new Schema("my-schema") new Schema("my-schema")
.AddNumber(1, "my-field1", Partitioning.Invariant, .AddNumber(1, "my-field1", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = true }) new NumberFieldProperties { IsRequired = true })
.AddNumber(2, "my-field2", Partitioning.Invariant, .AddNumber(2, "my-field2", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = false }); new NumberFieldProperties { IsRequired = false })
.ConfigureScripts(scripts);
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig);
@ -87,10 +97,6 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema)); A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema));
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
A.CallTo(() => schema.ScriptCreate).Returns("<create-script>");
A.CallTo(() => schema.ScriptChange).Returns("<change-script>");
A.CallTo(() => schema.ScriptUpdate).Returns("<update-script>");
A.CallTo(() => schema.ScriptDelete).Returns("<delete-script>");
A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored)) A.CallTo(() => scriptEngine.ExecuteAndTransform(A<ScriptContext>.Ignored, A<string>.Ignored))
.ReturnsLazily(x => x.GetArgument<ScriptContext>(0).Data); .ReturnsLazily(x => x.GetArgument<ScriptContext>(0).Data);

67
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -42,12 +42,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
private readonly IAppEntity app = A.Fake<IAppEntity>(); private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly IAssetUrlGenerator urlGenerator = A.Fake<IAssetUrlGenerator>(); private readonly IAssetUrlGenerator urlGenerator = A.Fake<IAssetUrlGenerator>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid schemaId = Guid.NewGuid();
private readonly Guid contentId = Guid.NewGuid(); private readonly Guid contentId = Guid.NewGuid();
private readonly string appName = "my-app"; private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly string schemaName = "my-schema"; private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly string script = "<script-query>";
private readonly NamedContentData contentData = new NamedContentData(); private readonly NamedContentData contentData = new NamedContentData();
private readonly NamedContentData contentTransformed = new NamedContentData(); private readonly NamedContentData contentTransformed = new NamedContentData();
private readonly ClaimsPrincipal user; private readonly ClaimsPrincipal user;
@ -60,15 +57,17 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
user = new ClaimsPrincipal(identity); user = new ClaimsPrincipal(identity);
A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English); A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
A.CallTo(() => schema.AppId).Returns(new NamedId<Guid>(appId, appName)); var schemaDef =
A.CallTo(() => schema.Id).Returns(schemaId); new Schema(schemaId.Name)
A.CallTo(() => schema.Name).Returns(schemaName); .ConfigureScripts(new Dictionary<string, string> { [Scripts.Query] = "<script-query>" });
A.CallTo(() => schema.SchemaDef).Returns(new Schema(schemaName));
A.CallTo(() => schema.ScriptQuery).Returns(script); A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
context = QueryContext.Create(app, user); context = QueryContext.Create(app, user);
@ -86,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
SetupSchema(); SetupSchema();
var result = await sut.GetSchemaAsync(context, schemaId.ToString()); var result = await sut.GetSchemaAsync(context, schemaId.Name);
Assert.Equal(schema, result); Assert.Equal(schema, result);
} }
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
SetupSchema(); SetupSchema();
var result = await sut.GetSchemaAsync(context, schemaName); var result = await sut.GetSchemaAsync(context, schemaId.Name);
Assert.Equal(schema, result); Assert.Equal(schema, result);
} }
@ -108,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(ctx, schemaName)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(ctx, schemaId.Name));
} }
[Fact] [Fact]
@ -118,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.ThrowIfSchemaNotExistsAsync(ctx, schemaName)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.ThrowIfSchemaNotExistsAsync(ctx, schemaId.Name));
} }
public static IEnumerable<object[]> SingleDataFrontend = new[] public static IEnumerable<object[]> SingleDataFrontend = new[]
@ -141,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindContentAsync(ctx, schemaId.ToString(), contentId)); await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindContentAsync(ctx, schemaId.Name, contentId));
} }
[Fact] [Fact]
@ -155,7 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(async () => await sut.FindContentAsync(ctx, schemaId.ToString(), contentId)); await Assert.ThrowsAsync<DomainObjectNotFoundException>(async () => await sut.FindContentAsync(ctx, schemaId.Name, contentId));
} }
[Theory] [Theory]
@ -173,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithUnpublished(unpublished); var ctx = context.WithUnpublished(unpublished);
var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId); var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId);
Assert.Equal(contentTransformed, result.Data); Assert.Equal(contentTransformed, result.Data);
Assert.Equal(content.Id, result.Id); Assert.Equal(content.Id, result.Id);
@ -197,7 +196,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithUnpublished(unpublished); var ctx = context.WithUnpublished(unpublished);
var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId); var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId);
Assert.Equal(contentTransformed, result.Data); Assert.Equal(contentTransformed, result.Data);
Assert.Equal(content.Id, result.Id); Assert.Equal(content.Id, result.Id);
@ -220,7 +219,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
var result = await sut.FindContentAsync(ctx, schemaId.ToString(), contentId, 10); var result = await sut.FindContentAsync(ctx, schemaId.Name, contentId, 10);
Assert.Equal(contentTransformed, result.Data); Assert.Equal(contentTransformed, result.Data);
Assert.Equal(content.Id, result.Id); Assert.Equal(content.Id, result.Id);
@ -250,7 +249,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context; var ctx = context;
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty)); await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.QueryAsync(ctx, schemaId.Name, Q.Empty));
} }
[Theory] [Theory]
@ -268,7 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithArchived(archive).WithUnpublished(unpublished); var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
Assert.Equal(contentData, result[0].Data); Assert.Equal(contentData, result[0].Data);
Assert.Equal(content.Id, result[0].Id); Assert.Equal(content.Id, result[0].Id);
@ -294,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithArchived(archive).WithUnpublished(unpublished); var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty);
Assert.Equal(contentData, result[0].Data); Assert.Equal(contentData, result[0].Data);
Assert.Equal(contentId, result[0].Id); Assert.Equal(contentId, result[0].Id);
@ -314,7 +313,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) A.CallTo(() => modelBuilder.BuildEdmModel(schema, app))
.Throws(new ODataException()); .Throws(new ODataException());
return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.ToString(), Q.Empty.WithODataQuery("query"))); return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.Name, Q.Empty.WithODataQuery("query")));
} }
public static IEnumerable<object[]> ManyIdDataFrontend = new[] public static IEnumerable<object[]> ManyIdDataFrontend = new[]
@ -348,7 +347,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithArchived(archive).WithUnpublished(unpublished); var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty.WithIds(ids)); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids));
Assert.Equal(ids, result.Select(x => x.Id).ToList()); Assert.Equal(ids, result.Select(x => x.Id).ToList());
Assert.Equal(total, result.Total); Assert.Equal(total, result.Total);
@ -372,7 +371,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context.WithArchived(archive).WithUnpublished(unpublished); var ctx = context.WithArchived(archive).WithUnpublished(unpublished);
var result = await sut.QueryAsync(ctx, schemaId.ToString(), Q.Empty.WithIds(ids)); var result = await sut.QueryAsync(ctx, schemaId.Name, Q.Empty.WithIds(ids));
Assert.Equal(ids, result.Select(x => x.Id).ToList()); Assert.Equal(ids, result.Select(x => x.Id).ToList());
Assert.Equal(total, result.Total); Assert.Equal(total, result.Total);
@ -390,7 +389,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
if (allowSchema) if (allowSchema)
{ {
identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.ForApp(Permissions.AppContentsRead, app.Name, schema.Name).Id)); identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.ForApp(Permissions.AppContentsRead, app.Name, schema.SchemaDef.Name).Id));
} }
} }
@ -398,7 +397,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
foreach (var id in ids) foreach (var id in ids)
{ {
A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), script)) A.CallTo(() => scriptEngine.Transform(A<ScriptContext>.That.Matches(x => x.User == user && x.ContentId == id && x.Data == contentData), "<script-query>"))
.Returns(contentTransformed); .Returns(contentTransformed);
} }
} }
@ -417,19 +416,19 @@ namespace Squidex.Domain.Apps.Entities.Contents
private void SetupSchema() private void SetupSchema()
{ {
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))
.Returns(schema); .Returns(schema);
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName)) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(schema); .Returns(schema);
} }
private void SetupSchemaNotFound() private void SetupSchemaNotFound()
{ {
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName)) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
} }
@ -441,7 +440,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => content.Data).Returns(contentData); A.CallTo(() => content.Data).Returns(contentData);
A.CallTo(() => content.DataDraft).Returns(contentData); A.CallTo(() => content.DataDraft).Returns(contentData);
A.CallTo(() => content.Status).Returns(status); A.CallTo(() => content.Status).Returns(status);
A.CallTo(() => content.SchemaId).Returns(new NamedId<Guid>(schemaId, schemaName)); A.CallTo(() => content.SchemaId).Returns(schemaId);
return content; return content;
} }

13
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -33,10 +33,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public class GraphQLTestBase public class GraphQLTestBase
{ {
protected static readonly Guid schemaId = Guid.NewGuid();
protected static readonly Guid appId = Guid.NewGuid();
protected static readonly string appName = "my-app";
protected readonly Schema schemaDef; protected readonly Schema schemaDef;
protected readonly Guid schemaId = Guid.NewGuid();
protected readonly Guid appId = Guid.NewGuid();
protected readonly string appName = "my-app";
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>(); protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); protected readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>(); protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
@ -76,7 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new StringFieldProperties()) new StringFieldProperties())
.AddArray(13, "my-array", Partitioning.Invariant, f => f .AddArray(13, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean") .AddBoolean(121, "nested-boolean")
.AddNumber(122, "nested-number")); .AddNumber(122, "nested-number"))
.ConfigureScripts(new Dictionary<string, string> { [Scripts.Query] = "<script-query>" })
.Publish();
A.CallTo(() => app.Id).Returns(appId); A.CallTo(() => app.Id).Returns(appId);
A.CallTo(() => app.Name).Returns(appName); A.CallTo(() => app.Name).Returns(appName);
@ -85,10 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
context = QueryContext.Create(app, user); context = QueryContext.Create(app, user);
A.CallTo(() => schema.Id).Returns(schemaId); A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.Name).Returns(schemaDef.Name);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
A.CallTo(() => schema.IsPublished).Returns(true);
A.CallTo(() => schema.ScriptQuery).Returns("<script-query>");
var allSchemas = new List<ISchemaEntity> { schema }; var allSchemas = new List<ISchemaEntity> { schema };

43
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -8,6 +8,7 @@
using FakeItEasy; using FakeItEasy;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Guards; using Squidex.Domain.Apps.Entities.Contents.Guards;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
@ -25,6 +26,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanCreate_should_throw_exception_if_data_is_null() public void CanCreate_should_throw_exception_if_data_is_null()
{ {
SetupSingleton(false);
var command = new CreateContent(); var command = new CreateContent();
ValidationAssert.Throws(() => GuardContent.CanCreate(schema, command), ValidationAssert.Throws(() => GuardContent.CanCreate(schema, command),
@ -34,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanCreate_should_throw_exception_if_singleton() public void CanCreate_should_throw_exception_if_singleton()
{ {
A.CallTo(() => schema.IsSingleton).Returns(true); SetupSingleton(true);
var command = new CreateContent { Data = new NamedContentData() }; var command = new CreateContent { Data = new NamedContentData() };
@ -44,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id() public void CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id()
{ {
A.CallTo(() => schema.IsSingleton).Returns(true); SetupSingleton(true);
var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id }; var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id };
@ -54,6 +57,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanCreate_should_not_throw_exception_if_data_is_not_null() public void CanCreate_should_not_throw_exception_if_data_is_not_null()
{ {
SetupSingleton(false);
var command = new CreateContent { Data = new NamedContentData() }; var command = new CreateContent { Data = new NamedContentData() };
GuardContent.CanCreate(schema, command); GuardContent.CanCreate(schema, command);
@ -62,6 +67,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanUpdate_should_throw_exception_if_data_is_null() public void CanUpdate_should_throw_exception_if_data_is_null()
{ {
SetupSingleton(false);
var command = new UpdateContent(); var command = new UpdateContent();
ValidationAssert.Throws(() => GuardContent.CanUpdate(command), ValidationAssert.Throws(() => GuardContent.CanUpdate(command),
@ -71,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanUpdate_should_not_throw_exception_if_data_is_not_null() public void CanUpdate_should_not_throw_exception_if_data_is_not_null()
{ {
SetupSingleton(false);
var command = new UpdateContent { Data = new NamedContentData() }; var command = new UpdateContent { Data = new NamedContentData() };
GuardContent.CanUpdate(command); GuardContent.CanUpdate(command);
@ -79,6 +88,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanPatch_should_throw_exception_if_data_is_null() public void CanPatch_should_throw_exception_if_data_is_null()
{ {
SetupSingleton(false);
var command = new PatchContent(); var command = new PatchContent();
ValidationAssert.Throws(() => GuardContent.CanPatch(command), ValidationAssert.Throws(() => GuardContent.CanPatch(command),
@ -88,6 +99,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanPatch_should_not_throw_exception_if_data_is_not_null() public void CanPatch_should_not_throw_exception_if_data_is_not_null()
{ {
SetupSingleton(false);
var command = new PatchContent { Data = new NamedContentData() }; var command = new PatchContent { Data = new NamedContentData() };
GuardContent.CanPatch(command); GuardContent.CanPatch(command);
@ -96,6 +109,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_throw_exception_if_status_not_valid() public void CanChangeContentStatus_should_throw_exception_if_status_not_valid()
{ {
SetupSingleton(false);
var command = new ChangeContentStatus { Status = (Status)10 }; var command = new ChangeContentStatus { Status = (Status)10 };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command), ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command),
@ -105,6 +120,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_throw_exception_if_status_flow_not_valid() public void CanChangeContentStatus_should_throw_exception_if_status_flow_not_valid()
{ {
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published }; var command = new ChangeContentStatus { Status = Status.Published };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command), ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command),
@ -114,6 +131,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past() public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past()
{ {
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast }; var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command), ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command),
@ -123,6 +142,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_throw_exception_if_publishing_without_pending_changes() public void CanChangeContentStatus_should_throw_exception_if_publishing_without_pending_changes()
{ {
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published }; var command = new ChangeContentStatus { Status = Status.Published };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Published, command), ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Published, command),
@ -132,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_throw_exception_if_singleton() public void CanChangeContentStatus_should_throw_exception_if_singleton()
{ {
A.CallTo(() => schema.IsSingleton).Returns(true); SetupSingleton(true);
var command = new ChangeContentStatus { Status = Status.Draft }; var command = new ChangeContentStatus { Status = Status.Draft };
@ -142,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_not_throw_exception_if_publishing_with_pending_changes() public void CanChangeContentStatus_should_not_throw_exception_if_publishing_with_pending_changes()
{ {
A.CallTo(() => schema.IsSingleton).Returns(true); SetupSingleton(true);
var command = new ChangeContentStatus { Status = Status.Published }; var command = new ChangeContentStatus { Status = Status.Published };
@ -152,6 +173,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanChangeContentStatus_should_not_throw_exception_if_status_flow_valid() public void CanChangeContentStatus_should_not_throw_exception_if_status_flow_valid()
{ {
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published }; var command = new ChangeContentStatus { Status = Status.Published };
GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command); GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command);
@ -168,6 +191,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanDiscardChanges_should_not_throw_exception_if_pending() public void CanDiscardChanges_should_not_throw_exception_if_pending()
{ {
SetupSingleton(false);
var command = new DiscardChanges(); var command = new DiscardChanges();
GuardContent.CanDiscardChanges(true, command); GuardContent.CanDiscardChanges(true, command);
@ -176,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanDelete_should_throw_exception_if_singleton() public void CanDelete_should_throw_exception_if_singleton()
{ {
A.CallTo(() => schema.IsSingleton).Returns(true); SetupSingleton(true);
var command = new DeleteContent(); var command = new DeleteContent();
@ -186,9 +211,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact] [Fact]
public void CanDelete_should_not_throw_exception() public void CanDelete_should_not_throw_exception()
{ {
SetupSingleton(false);
var command = new DeleteContent(); var command = new DeleteContent();
GuardContent.CanDelete(schema, command); GuardContent.CanDelete(schema, command);
} }
private void SetupSingleton(bool isSingleton)
{
A.CallTo(() => schema.SchemaDef)
.Returns(new Schema("schema", isSingleton: isSingleton));
}
} }
} }

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs

@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData
public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content) public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content)
{ {
return $"contents/{schema.Name}/{content.Id}"; return $"contents/{schema.SchemaDef.Name}/{content.Id}";
} }
} }
} }

78
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs

@ -64,9 +64,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "invalid name", Name = "invalid name",
Properties = new StringFieldProperties(), Properties = new StringFieldProperties(),
@ -87,9 +87,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = null, Properties = null,
@ -110,9 +110,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 }, Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 },
@ -134,9 +134,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = new StringFieldProperties(), Properties = new StringFieldProperties(),
@ -157,15 +157,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = new StringFieldProperties(), Properties = new StringFieldProperties(),
Partitioning = Partitioning.Invariant.Key Partitioning = Partitioning.Invariant.Key
}, },
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = new StringFieldProperties(), Properties = new StringFieldProperties(),
@ -186,16 +186,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "array", Name = "array",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key, Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "invalid name", Name = "invalid name",
Properties = new StringFieldProperties() Properties = new StringFieldProperties()
@ -217,16 +217,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "array", Name = "array",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key, Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = null Properties = null
@ -248,16 +248,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "array", Name = "array",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key, Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = new ArrayFieldProperties() Properties = new ArrayFieldProperties()
@ -279,16 +279,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "array", Name = "array",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key, Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 } Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 }
@ -311,21 +311,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "array", Name = "array",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key, Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = new StringFieldProperties() Properties = new StringFieldProperties()
}, },
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = new StringFieldProperties() Properties = new StringFieldProperties()
@ -347,33 +347,33 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema var command = new CreateSchema
{ {
AppId = appId, AppId = appId,
Fields = new List<CreateSchemaField> Fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field1", Name = "field1",
Properties = ValidProperties(), Properties = ValidProperties(),
Partitioning = "invariant" Partitioning = "invariant"
}, },
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field2", Name = "field2",
Properties = ValidProperties(), Properties = ValidProperties(),
Partitioning = "invariant" Partitioning = "invariant"
}, },
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field3", Name = "field3",
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Partitioning = "invariant", Partitioning = "invariant",
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested1", Name = "nested1",
Properties = ValidProperties() Properties = ValidProperties()
}, },
new CreateSchemaNestedField new UpsertSchemaNestedField
{ {
Name = "nested2", Name = "nested2",
Properties = ValidProperties() Properties = ValidProperties()

39
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasByAppIndexGrainTests.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Xunit; using Xunit;
@ -18,30 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{ {
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>(); private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly IPersistence<SchemasByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<SchemasByAppIndexGrain.GrainState>>(); private readonly IPersistence<SchemasByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<SchemasByAppIndexGrain.GrainState>>();
private readonly Guid appId = Guid.NewGuid(); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly Guid schemaId1 = Guid.NewGuid(); private readonly NamedId<Guid> schemaId1 = NamedId.Of(Guid.NewGuid(), "my-schema1");
private readonly Guid schemaId2 = Guid.NewGuid(); private readonly NamedId<Guid> schemaId2 = NamedId.Of(Guid.NewGuid(), "my-schema2");
private readonly string schemaName1 = "my-schema1";
private readonly string schemaName2 = "my-schema2";
private readonly SchemasByAppIndexGrain sut; private readonly SchemasByAppIndexGrain sut;
public SchemasByAppIndexGrainTests() public SchemasByAppIndexGrainTests()
{ {
A.CallTo(() => store.WithSnapshots(typeof(SchemasByAppIndexGrain), appId, A<HandleSnapshot<SchemasByAppIndexGrain.GrainState>>.Ignored)) A.CallTo(() => store.WithSnapshots(typeof(SchemasByAppIndexGrain), appId.Id, A<HandleSnapshot<SchemasByAppIndexGrain.GrainState>>.Ignored))
.Returns(persistence); .Returns(persistence);
sut = new SchemasByAppIndexGrain(store); sut = new SchemasByAppIndexGrain(store);
sut.ActivateAsync(appId).Wait(); sut.ActivateAsync(appId.Id).Wait();
} }
[Fact] [Fact]
public async Task Should_add_schema_id_to_index() public async Task Should_add_schema_id_to_index()
{ {
await sut.AddSchemaAsync(schemaId1, schemaName1); await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name);
var result = await sut.GetSchemaIdAsync(schemaName1); var result = await sut.GetSchemaIdAsync(schemaId1.Name);
Assert.Equal(schemaId1, result); Assert.Equal(schemaId1.Id, result);
A.CallTo(() => persistence.WriteSnapshotAsync(A<SchemasByAppIndexGrain.GrainState>.Ignored)) A.CallTo(() => persistence.WriteSnapshotAsync(A<SchemasByAppIndexGrain.GrainState>.Ignored))
.MustHaveHappened(); .MustHaveHappened();
@ -50,10 +49,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
[Fact] [Fact]
public async Task Should_delete_and_reset_state_when_cleaning() public async Task Should_delete_and_reset_state_when_cleaning()
{ {
await sut.AddSchemaAsync(schemaId1, schemaName1); await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name);
await sut.ClearAsync(); await sut.ClearAsync();
var id = await sut.GetSchemaIdAsync(schemaName1); var id = await sut.GetSchemaIdAsync(schemaId1.Name);
Assert.Equal(id, Guid.Empty); Assert.Equal(id, Guid.Empty);
@ -64,10 +63,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
[Fact] [Fact]
public async Task Should_remove_schema_id_from_index() public async Task Should_remove_schema_id_from_index()
{ {
await sut.AddSchemaAsync(schemaId1, schemaName1); await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name);
await sut.RemoveSchemaAsync(schemaId1); await sut.RemoveSchemaAsync(schemaId1.Id);
var result = await sut.GetSchemaIdAsync(schemaName1); var result = await sut.GetSchemaIdAsync(schemaId1.Name);
Assert.Equal(Guid.Empty, result); Assert.Equal(Guid.Empty, result);
@ -80,16 +79,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{ {
var state = new Dictionary<string, Guid> var state = new Dictionary<string, Guid>
{ {
[schemaName1] = schemaId1, [schemaId1.Name] = schemaId1.Id,
[schemaName2] = schemaId2 [schemaId2.Name] = schemaId2.Id
}; };
await sut.RebuildAsync(state); await sut.RebuildAsync(state);
Assert.Equal(schemaId1, await sut.GetSchemaIdAsync(schemaName1)); Assert.Equal(schemaId1.Id, await sut.GetSchemaIdAsync(schemaId1.Name));
Assert.Equal(schemaId2, await sut.GetSchemaIdAsync(schemaName2)); Assert.Equal(schemaId2.Id, await sut.GetSchemaIdAsync(schemaId2.Name));
Assert.Equal(new List<Guid> { schemaId1, schemaId2 }, await sut.GetSchemaIdsAsync()); Assert.Equal(new List<Guid> { schemaId1.Id, schemaId2.Id }, await sut.GetSchemaIdsAsync());
A.CallTo(() => persistence.WriteSnapshotAsync(A<SchemasByAppIndexGrain.GrainState>.Ignored)) A.CallTo(() => persistence.WriteSnapshotAsync(A<SchemasByAppIndexGrain.GrainState>.Ignored))
.MustHaveHappened(); .MustHaveHappened();

49
tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaGrainTests.cs

@ -26,7 +26,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public class SchemaGrainTests : HandlerTestBase<SchemaState> public class SchemaGrainTests : HandlerTestBase<SchemaState>
{ {
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry());
private readonly string fieldName = "age"; private readonly string fieldName = "age";
private readonly string arrayName = "array"; private readonly string arrayName = "array";
private readonly NamedId<long> fieldId = NamedId.Of(1L, "age"); private readonly NamedId<long> fieldId = NamedId.Of(1L, "age");
@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
A.CallTo(() => appProvider.GetSchemaAsync(AppId, SchemaName)) A.CallTo(() => appProvider.GetSchemaAsync(AppId, SchemaName))
.Returns((ISchemaEntity)null); .Returns((ISchemaEntity)null);
sut = new SchemaGrain(Store, A.Dummy<ISemanticLog>(), appProvider, registry); sut = new SchemaGrain(Store, A.Dummy<ISemanticLog>(), appProvider, TestUtils.DefaultSerializer);
sut.ActivateAsync(Id).Wait(); sut.ActivateAsync(Id).Wait();
} }
@ -70,13 +69,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas
Assert.Equal(AppId, sut.Snapshot.AppId.Id); Assert.Equal(AppId, sut.Snapshot.AppId.Id);
Assert.Equal(SchemaName, sut.Snapshot.Name);
Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name);
Assert.True(sut.Snapshot.IsSingleton); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name);
Assert.True(sut.Snapshot.SchemaDef.IsSingleton);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new SchemaCreated { Name = SchemaName, Properties = properties, Singleton = true }) CreateEvent(new SchemaCreated { Schema = new Schema(command.Name, command.Properties, command.Singleton) })
); );
} }
@ -85,19 +84,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var properties = new SchemaProperties(); var properties = new SchemaProperties();
var fields = new List<CreateSchemaField> var fields = new List<UpsertSchemaField>
{ {
new CreateSchemaField { Name = "field1", Properties = ValidProperties() }, new UpsertSchemaField { Name = "field1", Properties = ValidProperties() },
new CreateSchemaField { Name = "field2", Properties = ValidProperties() }, new UpsertSchemaField { Name = "field2", Properties = ValidProperties() },
new CreateSchemaField new UpsertSchemaField
{ {
Name = "field3", Name = "field3",
Partitioning = Partitioning.Language.Key, Partitioning = Partitioning.Language.Key,
Properties = new ArrayFieldProperties(), Properties = new ArrayFieldProperties(),
Nested = new List<CreateSchemaNestedField> Nested = new List<UpsertSchemaNestedField>
{ {
new CreateSchemaNestedField { Name = "nested1", Properties = ValidProperties() }, new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() },
new CreateSchemaNestedField { Name = "nested2", Properties = ValidProperties() } new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() }
} }
} }
}; };
@ -111,10 +110,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var @event = (SchemaCreated)LastEvents.Single().Payload; var @event = (SchemaCreated)LastEvents.Single().Payload;
Assert.Equal(AppId, sut.Snapshot.AppId.Id); Assert.Equal(AppId, sut.Snapshot.AppId.Id);
Assert.Equal(SchemaName, sut.Snapshot.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name);
Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name); Assert.Equal(SchemaName, sut.Snapshot.SchemaDef.Name);
Assert.Equal(3, @event.Fields.Count); Assert.Equal(3, @event.Schema.Fields.Count);
} }
[Fact] [Fact]
@ -141,11 +140,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{ {
var command = new ConfigureScripts var command = new ConfigureScripts
{ {
ScriptQuery = "<script-query>", Scripts = new Dictionary<string, string>
ScriptCreate = "<script-create>", {
ScriptUpdate = "<script-update>", ["Query"] = "<script-query>"
ScriptDelete = "<script-delete>", }
ScriptChange = "<script-change>"
}; };
await ExecuteCreateAsync(); await ExecuteCreateAsync();
@ -156,14 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
CreateEvent(new ScriptsConfigured CreateEvent(new SchemaScriptsConfigured { Scripts = command.Scripts })
{
ScriptQuery = "<script-query>",
ScriptCreate = "<script-create>",
ScriptUpdate = "<script-update>",
ScriptDelete = "<script-delete>",
ScriptChange = "<script-change>"
})
); );
} }
@ -217,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
result.ShouldBeEquivalent(new EntitySavedResult(1)); result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(command.Name, sut.Snapshot.Category); Assert.Equal(command.Name, sut.Snapshot.SchemaDef.Category);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(
@ -242,7 +233,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
result.ShouldBeEquivalent(new EntitySavedResult(1)); result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(command.PreviewUrls, sut.Snapshot.PreviewUrls); Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls);
LastEvents LastEvents
.ShouldHaveSameEvents( .ShouldHaveSameEvents(

38
tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddlewareTests.cs

@ -22,6 +22,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
{ {
private readonly IHttpContextAccessor httpContextAccessor = A.Fake<IHttpContextAccessor>(); private readonly IHttpContextAccessor httpContextAccessor = A.Fake<IHttpContextAccessor>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly HttpContext httpContext = new DefaultHttpContext(); private readonly HttpContext httpContext = new DefaultHttpContext();
private readonly EnrichWithAppIdCommandMiddleware sut; private readonly EnrichWithAppIdCommandMiddleware sut;
@ -30,12 +31,21 @@ namespace Squidex.Pipeline.CommandMiddlewares
A.CallTo(() => httpContextAccessor.HttpContext) A.CallTo(() => httpContextAccessor.HttpContext)
.Returns(httpContext); .Returns(httpContext);
var appEntity = A.Fake<IAppEntity>();
A.CallTo(() => appEntity.Id).Returns(appId.Id);
A.CallTo(() => appEntity.Name).Returns(appId.Name);
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(appEntity));
sut = new EnrichWithAppIdCommandMiddleware(httpContextAccessor); sut = new EnrichWithAppIdCommandMiddleware(httpContextAccessor);
} }
[Fact] [Fact]
public async Task Should_throw_exception_if_app_not_found() public async Task Should_throw_exception_if_app_not_found()
{ {
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(null));
var command = new CreateContent(); var command = new CreateContent();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
@ -59,65 +69,45 @@ namespace Squidex.Pipeline.CommandMiddlewares
[Fact] [Fact]
public async Task Should_assign_app_id_and_name_to_app_command() public async Task Should_assign_app_id_and_name_to_app_command()
{ {
SetupApp(out var appId, out var appName);
var command = new CreateContent(); var command = new CreateContent();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(NamedId.Of(appId, appName), command.AppId); Assert.Equal(appId, command.AppId);
} }
[Fact] [Fact]
public async Task Should_assign_app_id_to_app_self_command() public async Task Should_assign_app_id_to_app_self_command()
{ {
SetupApp(out var appId, out _);
var command = new AddPattern(); var command = new AddPattern();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(appId, command.AppId); Assert.Equal(appId.Id, command.AppId);
} }
[Fact] [Fact]
public async Task Should_not_override_app_id() public async Task Should_not_override_app_id()
{ {
SetupApp(out var appId, out _);
var command = new AddPattern { AppId = Guid.NewGuid() }; var command = new AddPattern { AppId = Guid.NewGuid() };
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.NotEqual(appId, command.AppId); Assert.NotEqual(appId.Id, command.AppId);
} }
[Fact] [Fact]
public async Task Should_not_override_app_id_and_name() public async Task Should_not_override_app_id_and_name()
{ {
SetupApp(out var appId, out var appName);
var command = new CreateContent { AppId = NamedId.Of(Guid.NewGuid(), "other-app") }; var command = new CreateContent { AppId = NamedId.Of(Guid.NewGuid(), "other-app") };
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.NotEqual(NamedId.Of(appId, appName), command.AppId); Assert.NotEqual(appId, command.AppId);
}
private void SetupApp(out Guid appId, out string appName)
{
appId = Guid.NewGuid();
appName = "my-app";
var appEntity = A.Fake<IAppEntity>();
A.CallTo(() => appEntity.Id).Returns(appId);
A.CallTo(() => appEntity.Name).Returns(appName);
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(appEntity));
} }
} }
} }

97
tests/Squidex.Tests/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddlewareTests.cs

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
@ -28,6 +29,8 @@ namespace Squidex.Pipeline.CommandMiddlewares
private readonly IActionContextAccessor actionContextAccessor = A.Fake<IActionContextAccessor>(); private readonly IActionContextAccessor actionContextAccessor = A.Fake<IActionContextAccessor>();
private readonly IAppProvider appProvider = A.Fake<IAppProvider>(); private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly ICommandBus commandBus = A.Fake<ICommandBus>(); private readonly ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly HttpContext httpContext = new DefaultHttpContext(); private readonly HttpContext httpContext = new DefaultHttpContext();
private readonly ActionContext actionContext = new ActionContext(); private readonly ActionContext actionContext = new ActionContext();
private readonly EnrichWithSchemaIdCommandMiddleware sut; private readonly EnrichWithSchemaIdCommandMiddleware sut;
@ -40,16 +43,30 @@ namespace Squidex.Pipeline.CommandMiddlewares
A.CallTo(() => actionContextAccessor.ActionContext) A.CallTo(() => actionContextAccessor.ActionContext)
.Returns(actionContext); .Returns(actionContext);
var appEntity = A.Fake<IAppEntity>();
A.CallTo(() => appEntity.Id).Returns(appId.Id);
A.CallTo(() => appEntity.Name).Returns(appId.Name);
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(appEntity));
var schemaEntity = A.Fake<ISchemaEntity>();
A.CallTo(() => schemaEntity.Id).Returns(schemaId.Id);
A.CallTo(() => schemaEntity.SchemaDef).Returns(new Schema(schemaId.Name));
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))
.Returns(schemaEntity);
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(schemaEntity);
sut = new EnrichWithSchemaIdCommandMiddleware(appProvider, actionContextAccessor); sut = new EnrichWithSchemaIdCommandMiddleware(appProvider, actionContextAccessor);
} }
[Fact] [Fact]
public async Task Should_throw_exception_if_app_not_found() public async Task Should_throw_exception_if_app_not_found()
{ {
SetupApp(out var appId, out _); A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, "other-schema"))
SetupSchema(appId, out _, out _);
A.CallTo(() => appProvider.GetSchemaAsync(appId, "other-schema"))
.Returns(Task.FromResult<ISchemaEntity>(null)); .Returns(Task.FromResult<ISchemaEntity>(null));
actionContext.RouteData.Values["name"] = "other-schema"; actionContext.RouteData.Values["name"] = "other-schema";
@ -77,9 +94,6 @@ namespace Squidex.Pipeline.CommandMiddlewares
[Fact] [Fact]
public async Task Should_do_nothing_when_route_has_no_parameter() public async Task Should_do_nothing_when_route_has_no_parameter()
{ {
SetupApp(out var appId, out _);
SetupSchema(appId, out _, out _);
var command = new CreateContent(); var command = new CreateContent();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
@ -91,124 +105,75 @@ namespace Squidex.Pipeline.CommandMiddlewares
[Fact] [Fact]
public async Task Should_assign_schema_id_and_name_from_name() public async Task Should_assign_schema_id_and_name_from_name()
{ {
SetupApp(out var appId, out _); actionContext.RouteData.Values["name"] = schemaId.Name;
SetupSchema(appId, out var schemaId, out var schemaName);
actionContext.RouteData.Values["name"] = schemaName;
var command = new CreateContent(); var command = new CreateContent();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); Assert.Equal(schemaId, command.SchemaId);
} }
[Fact] [Fact]
public async Task Should_assign_schema_id_and_name_from_id() public async Task Should_assign_schema_id_and_name_from_id()
{ {
SetupApp(out var appId, out _); actionContext.RouteData.Values["name"] = schemaId.Name;
SetupSchema(appId, out var schemaId, out var schemaName);
actionContext.RouteData.Values["name"] = schemaId.ToString();
var command = new CreateContent(); var command = new CreateContent();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); Assert.Equal(schemaId, command.SchemaId);
} }
[Fact] [Fact]
public async Task Should_assign_schema_id_from_id() public async Task Should_assign_schema_id_from_id()
{ {
SetupApp(out var appId, out _); actionContext.RouteData.Values["name"] = schemaId.Name;
SetupSchema(appId, out var schemaId, out _);
actionContext.RouteData.Values["name"] = schemaId.ToString();
var command = new UpdateSchema(); var command = new UpdateSchema();
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(schemaId, command.SchemaId); Assert.Equal(schemaId.Id, command.SchemaId);
} }
[Fact] [Fact]
public async Task Should_use_app_id_from_command() public async Task Should_use_app_id_from_command()
{ {
var appId = NamedId.Of(Guid.NewGuid(), "my-app"); actionContext.RouteData.Values["name"] = schemaId.Name;
SetupSchema(appId.Id, out var schemaId, out var schemaName);
actionContext.RouteData.Values["name"] = schemaId.ToString();
var command = new CreateContent { AppId = appId }; var command = new CreateContent { AppId = appId };
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.Equal(NamedId.Of(schemaId, schemaName), command.SchemaId); Assert.Equal(schemaId, command.SchemaId);
} }
[Fact] [Fact]
public async Task Should_not_override_schema_id() public async Task Should_not_override_schema_id()
{ {
SetupApp(out var appId, out _);
SetupSchema(appId, out var schemaId, out _);
var command = new CreateSchema { SchemaId = Guid.NewGuid() }; var command = new CreateSchema { SchemaId = Guid.NewGuid() };
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.NotEqual(schemaId, command.SchemaId); Assert.NotEqual(schemaId.Id, command.SchemaId);
} }
[Fact] [Fact]
public async Task Should_not_override_schema_id_and_name() public async Task Should_not_override_schema_id_and_name()
{ {
SetupApp(out var appId, out var appName);
SetupSchema(appId, out _, out _);
var command = new CreateContent { SchemaId = NamedId.Of(Guid.NewGuid(), "other-schema") }; var command = new CreateContent { SchemaId = NamedId.Of(Guid.NewGuid(), "other-schema") };
var context = new CommandContext(command, commandBus); var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context); await sut.HandleAsync(context);
Assert.NotEqual(NamedId.Of(appId, appName), command.AppId); Assert.NotEqual(appId, command.AppId);
}
private void SetupSchema(Guid appId, out Guid schemaId, out string schemaName)
{
schemaId = Guid.NewGuid();
schemaName = "my-schema";
var schemaEntity = A.Fake<ISchemaEntity>();
A.CallTo(() => schemaEntity.Id).Returns(schemaId);
A.CallTo(() => schemaEntity.Name).Returns(schemaName);
var temp1 = schemaName;
var temp2 = schemaId;
A.CallTo(() => appProvider.GetSchemaAsync(appId, temp1))
.Returns(schemaEntity);
A.CallTo(() => appProvider.GetSchemaAsync(appId, temp2, false))
.Returns(schemaEntity);
}
private void SetupApp(out Guid appId, out string appName)
{
appId = Guid.NewGuid();
appName = "my-app";
var appEntity = A.Fake<IAppEntity>();
A.CallTo(() => appEntity.Id).Returns(appId);
A.CallTo(() => appEntity.Name).Returns(appName);
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(appEntity));
} }
} }
} }

2
tools/Migrate_01/Migrations/PopulateGrainIndexes.cs

@ -106,7 +106,7 @@ namespace Migrate_01.Migrations
{ {
if (!schema.IsDeleted) if (!schema.IsDeleted)
{ {
schemasByApp.GetOrAddNew(schema.AppId.Id)[schema.Name] = schema.Id; schemasByApp.GetOrAddNew(schema.AppId.Id)[schema.SchemaDef.Name] = schema.Id;
} }
return TaskHelper.Done; return TaskHelper.Done;

106
tools/Migrate_01/OldEvents/SchemaCreated.cs

@ -0,0 +1,106 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using SchemaCreatedV2 = Squidex.Domain.Apps.Events.Schemas.SchemaCreated;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Events.Schemas.SchemaCreatedField>;
namespace Migrate_01.OldEvents
{
[EventType(nameof(SchemaCreated))]
[Obsolete]
public sealed class SchemaCreated : SchemaEvent, IMigrated<IEvent>
{
public string Name { get; set; }
public bool Singleton { get; set; }
public bool Publish { get; set; }
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public IEvent Migrate()
{
var schema = new Schema(Name, Properties, Singleton);
if (Publish)
{
schema = schema.Publish();
}
var totalFields = 0;
if (Fields != null)
{
foreach (var eventField in Fields)
{
totalFields++;
var partitioning = Partitioning.FromString(eventField.Partitioning);
var field = eventField.Properties.CreateRootField(totalFields, eventField.Name, partitioning);
if (field is ArrayField arrayField && eventField.Nested?.Count > 0)
{
foreach (var nestedEventField in eventField.Nested)
{
totalFields++;
var nestedField = nestedEventField.Properties.CreateNestedField(totalFields, nestedEventField.Name);
if (nestedEventField.IsHidden)
{
nestedField = nestedField.Hide();
}
if (nestedEventField.IsDisabled)
{
nestedField = nestedField.Disable();
}
if (nestedEventField.IsLocked)
{
nestedField = nestedField.Lock();
}
arrayField = arrayField.AddField(nestedField);
}
field = arrayField;
}
if (eventField.IsHidden)
{
field = field.Hide();
}
if (eventField.IsDisabled)
{
field = field.Disable();
}
if (eventField.IsLocked)
{
field = field.Lock();
}
schema = schema.AddField(field);
}
}
return SimpleMapper.Map(this, new SchemaCreatedV2 { Schema = schema });
}
}
}

65
tools/Migrate_01/OldEvents/ScriptsConfigured.cs

@ -0,0 +1,65 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
namespace Migrate_01.OldEvents
{
[EventType(nameof(ScriptsConfigured))]
[Obsolete]
public sealed class ScriptsConfigured : SchemaEvent, IMigrated<IEvent>
{
public string ScriptQuery { get; set; }
public string ScriptCreate { get; set; }
public string ScriptUpdate { get; set; }
public string ScriptDelete { get; set; }
public string ScriptChange { get; set; }
public IEvent Migrate()
{
var scripts = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(ScriptQuery))
{
scripts[Scripts.Query] = ScriptQuery;
}
if (!string.IsNullOrWhiteSpace(ScriptCreate))
{
scripts[Scripts.Create] = ScriptCreate;
}
if (!string.IsNullOrWhiteSpace(ScriptUpdate))
{
scripts[Scripts.Update] = ScriptUpdate;
}
if (!string.IsNullOrWhiteSpace(ScriptDelete))
{
scripts[Scripts.Delete] = ScriptDelete;
}
if (!string.IsNullOrWhiteSpace(ScriptChange))
{
scripts[Scripts.Change] = ScriptChange;
}
return SimpleMapper.Map(this, new SchemaScriptsConfigured { Scripts = scripts });
}
}
}

5
tools/Migrate_01/Rebuilder.cs

@ -31,18 +31,15 @@ namespace Migrate_01
{ {
public sealed class Rebuilder public sealed class Rebuilder
{ {
private readonly FieldRegistry fieldRegistry;
private readonly ILocalCache localCache; private readonly ILocalCache localCache;
private readonly IStore<Guid> store; private readonly IStore<Guid> store;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
public Rebuilder( public Rebuilder(
FieldRegistry fieldRegistry,
ILocalCache localCache, ILocalCache localCache,
IStore<Guid> store, IStore<Guid> store,
IEventStore eventStore) IEventStore eventStore)
{ {
this.fieldRegistry = fieldRegistry;
this.eventStore = eventStore; this.eventStore = eventStore;
this.localCache = localCache; this.localCache = localCache;
this.store = store; this.store = store;
@ -59,7 +56,7 @@ namespace Migrate_01
{ {
await store.GetSnapshotStore<SchemaState>().ClearAsync(); await store.GetSnapshotStore<SchemaState>().ClearAsync();
await RebuildManyAsync("^schema\\-", id => RebuildAsync<SchemaState, SchemaGrain>(id, (e, s) => s.Apply(e, fieldRegistry))); await RebuildManyAsync("^schema\\-", id => RebuildAsync<SchemaState, SchemaGrain>(id, (e, s) => s.Apply(e)));
} }
public async Task RebuildRulesAsync() public async Task RebuildRulesAsync()

Loading…
Cancel
Save