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. 17
      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.
// ==========================================================================
using Squidex.Infrastructure;
using System.Collections.Generic;
using NamedIdStatic = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Core.Schemas
{
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)
{
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
{
public sealed class FieldRegistry
public static class FieldRegistry
{
private readonly TypeNameRegistry typeNameRegistry;
private readonly HashSet<Type> supportedFields = new HashSet<Type>();
public FieldRegistry(TypeNameRegistry typeNameRegistry)
public static void Setup(TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
this.typeNameRegistry = typeNameRegistry;
var types = typeof(FieldRegistry).Assembly.GetTypes().Where(x => x.BaseType == typeof(FieldProperties));
var supportedFields = new HashSet<Type>();
foreach (var type in types)
{
RegisterField(type);
if (supportedFields.Add(type))
{
typeNameRegistry.Map(type);
}
}
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "References");
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]
public Schema MoveTo(string category)
public Schema ChangeCategory(string category)
{
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 Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using NamedIdStatic = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Core.EventSynchronization
{
@ -30,11 +27,6 @@ namespace Squidex.Domain.Apps.Core.EventSynchronization
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)
{
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>();
schemaNames.Add(Permission.Any);
schemaNames.AddRange(schemas.Select(x => x.Name));
schemaNames.AddRange(schemas.Select(x => x.SchemaDef.Name));
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 AssetFieldBuilder(CreateSchemaField field)
public AssetFieldBuilder(UpsertSchemaField 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 BooleanFieldBuilder(CreateSchemaField field)
public BooleanFieldBuilder(UpsertSchemaField 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 DateTimeFieldBuilder(CreateSchemaField field)
public DateTimeFieldBuilder(UpsertSchemaField 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
{
private readonly CreateSchemaField field;
private readonly UpsertSchemaField field;
protected T Properties<T>() where T : FieldProperties
{
return field.Properties as T;
}
protected FieldBuilder(CreateSchemaField field)
protected FieldBuilder(UpsertSchemaField 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 JsonFieldBuilder(CreateSchemaField field)
public JsonFieldBuilder(UpsertSchemaField 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 NumberFieldBuilder(CreateSchemaField field)
public NumberFieldBuilder(UpsertSchemaField 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;
}
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(),
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);
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 StringFieldBuilder(CreateSchemaField field)
public StringFieldBuilder(UpsertSchemaField 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 TagsFieldBuilder(CreateSchemaField field)
public TagsFieldBuilder(UpsertSchemaField 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
{
SchemaId = schemaId.Id,
ScriptCreate = Scripts.Slug,
ScriptUpdate = Scripts.Slug
Scripts = DefaultScripts.GenerateSlug
});
return schemaId;
@ -149,8 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
await publish(new ConfigureScripts
{
SchemaId = schemaId.Id,
ScriptCreate = Scripts.Slug,
ScriptUpdate = Scripts.Slug
Scripts = DefaultScripts.GenerateSlug
});
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
{
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)
{
@ -250,8 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
await publish(new ConfigureScripts
{
SchemaId = schemaId.Id,
ScriptCreate = NormalizeScript,
ScriptUpdate = NormalizeScript
Scripts = DefaultScripts.GenerateUsername
});
}

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);
strings[value] = result = new NamedId<Guid>(newGuid, namedId.Name).ToString();
strings[value] = result = NamedId.Of(newGuid, namedId.Name).ToString();
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))
{
appEvent.AppId = new NamedId<Guid>(appEvent.AppId.Id, CurrentJob.NewAppName);
appEvent.AppId = NamedId.Of(appEvent.AppId.Id, CurrentJob.NewAppName);
}
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);
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);
if (!c.DoNotValidate)
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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);
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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);
}
@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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);
});
@ -208,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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)
{

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 scriptText = schema.ScriptQuery;
var scriptText = schema.SchemaDef.Scripts.GetOrDefault("Query");
var isScripting = !string.IsNullOrWhiteSpace(scriptText);
@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private static void CheckPermission(ISchemaEntity schema, ClaimsPrincipal user)
{
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))
{

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);
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.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);
});
if (schema.IsSingleton && command.ContentId != schema.Id)
if (schema.SchemaDef.IsSingleton && command.ContentId != schema.Id)
{
throw new DomainException("Singleton content cannot be created.");
}
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{
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.");
}
@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
{
Guard.NotNull(command, nameof(command));
if (schema.IsSingleton)
if (schema.SchemaDef.IsSingleton)
{
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 &&
createSchema.Singleton)
{
var schemaId = new NamedId<Guid>(createSchema.SchemaId, createSchema.Name);
var schemaId = NamedId.Of(createSchema.SchemaId, createSchema.Name);
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
{
public sealed class AddField : SchemaCommand
public sealed class AddField : ParentFieldCommand
{
public long? ParentFieldId { get; set; }
public string Name { 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.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Domain.Apps.Entities.Schemas.Commands
{
public sealed class ConfigureScripts : SchemaCommand
{
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 Dictionary<string, string> Scripts { get; set; }
}
}

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

@ -8,27 +8,25 @@
using System;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using SchemaFields = System.Collections.Generic.List<Squidex.Domain.Apps.Entities.Schemas.Commands.CreateSchemaField>;
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 string Name { get; set; }
public SchemaFields Fields { get; set; }
public SchemaProperties Properties { get; set; }
public bool Singleton { get; set; }
public bool Publish { get; set; }
public CreateSchema()
{
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
{
public class FieldCommand : SchemaCommand
public class FieldCommand : ParentFieldCommand
{
public long? ParentFieldId { 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.
// ==========================================================================
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 =
@"var data = ctx.data;
if (data.title && data.title.iv) {
data.slug = { iv: slugify(data.title.iv) };
replace(data);
}
";
public long? ParentFieldId { get; set; }
}
}

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
{
public sealed class ReorderFields : SchemaCommand
public sealed class ReorderFields : ParentFieldCommand
{
public long? ParentFieldId { 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
{
public sealed class CreateSchemaField : CreateSchemaFieldBase
public sealed class UpsertSchemaField : UpsertSchemaFieldBase
{
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
{
public abstract class CreateSchemaFieldBase
public abstract class UpsertSchemaFieldBase
{
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
{
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.");
}
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(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)}");
}
ValidateUpsert(command, e);
});
}
if (field.Nested.Select(x => x.Name).Distinct().Count() != field.Nested.Count)
{
e("Fields cannot have duplicate names.", $"{fieldPrefix}.Nested");
}
}
}
public static void CanSynchronize(SynchronizeSchema command)
{
Guard.NotNull(command, nameof(command));
if (command.Fields.Select(x => x.Name).Distinct().Count() != command.Fields.Count)
{
e("Fields cannot have duplicate names.", nameof(command.Fields));
}
}
Validate.It(() => "Cannot synchronize schema.", e =>
{
ValidateUpsert(command, e);
});
}
@ -171,7 +128,65 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
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())
{

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

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
@ -20,28 +19,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
NamedId<Guid> AppId { get; }
string Name { get; }
string Category { get; }
bool IsSingleton { get; }
bool IsPublished { get; }
bool IsDeleted { get; }
string ScriptQuery { get; }
string ScriptCreate { get; }
string ScriptUpdate { get; }
string ScriptDelete { get; }
string ScriptChange { 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 Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using StaticNamedId = Squidex.Infrastructure.NamedId;
namespace Squidex.Domain.Apps.Entities.Schemas
{
@ -15,7 +16,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
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)

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

@ -6,8 +6,8 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.EventSynchronization;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Domain.Apps.Entities.Schemas.Guards;
@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection;
@ -27,17 +28,17 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public sealed class SchemaGrain : SquidexDomainObjectGrain<SchemaState>, ISchemaGrain
{
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)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(registry, nameof(registry));
Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider;
this.registry = registry;
this.serializer = serializer;
}
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) });
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);
var schemaSource = Snapshot.SchemaDef;
var schemaTarget = command.ToSchema(schemaSource.Name, schemaSource.IsSingleton);
if (commandField.Nested != null)
{
eventField.Nested = new List<SchemaCreatedNestedField>();
foreach (var nestedField in commandField.Nested)
{
var eventNestedField = SimpleMapper.Map(nestedField, new SchemaCreatedNestedField());
var @events = schemaTarget.Synchronize(schemaSource, serializer, () => Snapshot.SchemaFieldsTotal + 1);
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)
{
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)
{
RaiseEvent(command, SimpleMapper.Map(command, new FieldUpdated()));
RaiseEvent(command, new FieldUpdated());
}
public void LockField(LockField command)
@ -273,88 +261,86 @@ namespace Squidex.Domain.Apps.Entities.Schemas
public void Reorder(ReorderFields command)
{
RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered { ParentFieldId = GetFieldId(command.ParentFieldId) }));
RaiseEvent(command, new SchemaFieldsReordered());
}
public void Publish(PublishSchema command)
{
RaiseEvent(SimpleMapper.Map(command, new SchemaPublished()));
RaiseEvent(command, new SchemaPublished());
}
public void Unpublish(UnpublishSchema command)
{
RaiseEvent(SimpleMapper.Map(command, new SchemaUnpublished()));
RaiseEvent(command, new SchemaUnpublished());
}
public void ConfigureScripts(ConfigureScripts command)
{
RaiseEvent(SimpleMapper.Map(command, new ScriptsConfigured()));
RaiseEvent(command, new SchemaScriptsConfigured());
}
public void ChangeCategory(ChangeCategory command)
{
RaiseEvent(SimpleMapper.Map(command, new SchemaCategoryChanged()));
RaiseEvent(command, new SchemaCategoryChanged());
}
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
{
@event.FieldId = GetFieldId(fieldCommand.FieldId);
else if (command is FieldCommand fc && @event is FieldEvent fe)
{
fe.FieldId = GetFieldId(fc.FieldId);
}
}
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)
{
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)
@ -365,6 +351,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas
RaiseEvent(Envelope.Create(@event));
}
private NamedId<long> CreateFieldId(AddField command)
{
return NamedId.Of(Snapshot.SchemaFieldsTotal + 1, command.Name);
}
private void VerifyNotDeleted()
{
if (Snapshot.IsDeleted)
@ -375,7 +366,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
protected override SchemaState OnEvent(Envelope<IEvent> @event)
{
return Snapshot.Apply(@event, registry);
return Snapshot.Apply(@event);
}
public Task<J<ISchemaEntity>> GetStateAsync()

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

@ -6,7 +6,6 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
@ -15,7 +14,6 @@ using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.Schemas.State
@ -27,128 +25,27 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
public NamedId<Guid> AppId { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Category { get; set; }
[DataMember]
public int TotalFields { get; set; }
public long SchemaFieldsTotal { get; set; }
[DataMember]
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]
public Schema SchemaDef { get; set; }
[IgnoreDataMember]
public bool IsPublished
protected void On(SchemaCreated @event)
{
get { return SchemaDef.IsPublished; }
}
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;
SchemaDef = @event.Schema;
SchemaFieldsTotal = @event.Schema.MaxId();
AppId = @event.AppId;
}
protected void On(FieldAdded @event, FieldRegistry registry)
protected void On(FieldAdded @event)
{
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));
}
@ -156,95 +53,95 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State
{
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.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();
}
protected void On(SchemaUnpublished @event, FieldRegistry registry)
protected void On(SchemaUnpublished @event)
{
SchemaDef = SchemaDef.Unpublish();
}
protected void On(SchemaUpdated @event, FieldRegistry registry)
protected void On(SchemaUpdated @event)
{
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);
}
protected void On(FieldUpdated @event, FieldRegistry registry)
protected void On(FieldUpdated @event)
{
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);
}
protected void On(FieldDisabled @event, FieldRegistry registry)
protected void On(FieldDisabled @event)
{
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);
}
protected void On(FieldHidden @event, FieldRegistry registry)
protected void On(FieldHidden @event)
{
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);
}
protected void On(FieldDeleted @event, FieldRegistry registry)
protected void On(FieldDeleted @event)
{
SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id, @event.ParentFieldId?.Id);
}
protected void On(SchemaDeleted @event, FieldRegistry registry)
protected void On(SchemaDeleted @event)
{
IsDeleted = true;
}
protected void On(ScriptsConfigured @event, FieldRegistry registry)
{
SimpleMapper.Map(@event, this);
}
public SchemaState Apply(Envelope<IEvent> @event, FieldRegistry registry)
public SchemaState Apply(Envelope<IEvent> @event)
{
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
{
public abstract class FieldEvent : SchemaEvent
public abstract class FieldEvent : ParentFieldEvent
{
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.
// ==========================================================================
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Events.Schemas
{
[EventType(nameof(ScriptsConfigured))]
public sealed class ScriptsConfigured : SchemaEvent
public abstract class ParentFieldEvent : SchemaEvent
{
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 NamedId<long> ParentFieldId { get; set; }
}
}

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

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

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

@ -6,16 +6,13 @@
// ==========================================================================
using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Events.Schemas
{
[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; }
}
}

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

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

@ -57,21 +57,21 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
if (Fields != null)
{
command.Fields = new List<CreateSchemaField>();
command.Fields = new List<UpsertSchemaField>();
foreach (var fieldDto in Fields)
{
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)
{
rootField.Nested = new List<CreateSchemaNestedField>();
rootField.Nested = new List<UpsertSchemaNestedField>();
foreach (var nestedFieldDto in fieldDto.Nested)
{
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);
}

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

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

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

@ -97,7 +97,7 @@ namespace Squidex.Pipeline.CommandMiddlewares
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)
{
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)

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

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

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

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

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Xunit;
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
var schema = A.Fake<ISchemaEntity>();
A.CallTo(() => schema.Name).Returns(name);
A.CallTo(() => schema.SchemaDef).Returns(new Schema(name));
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 IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IAppEntity app = A.Fake<IAppEntity>();
private readonly Guid appId = Guid.NewGuid();
private readonly string appName = "my-app";
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly ClaimsIdentity identity = new ClaimsIdentity();
private readonly QueryContext context;
private readonly AssetQueryService sut;
@ -36,13 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var user = new ClaimsPrincipal(identity);
A.CallTo(() => app.Id).Returns(appId);
A.CallTo(() => app.Name).Returns(appName);
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
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>
{
["id1"] = "name1",
@ -74,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
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,
CreateAsset(id1, "id1", "id2", "id3"),
CreateAsset(id2)));
@ -91,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
[Fact]
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,
CreateAsset(Guid.NewGuid(), "id1", "id2"),
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"));
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();
}
@ -119,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
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();
}
@ -128,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
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();
}

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

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

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

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FakeItEasy;
using NodaTime;
@ -74,12 +75,21 @@ namespace Squidex.Domain.Apps.Entities.Contents
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 =
new Schema("my-schema")
.AddNumber(1, "my-field1", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = true })
.AddNumber(2, "my-field2", Partitioning.Invariant,
new NumberFieldProperties { IsRequired = false });
new NumberFieldProperties { IsRequired = false })
.ConfigureScripts(scripts);
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(() => 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))
.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 IAppProvider appProvider = A.Fake<IAppProvider>();
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 string appName = "my-app";
private readonly string schemaName = "my-schema";
private readonly string script = "<script-query>";
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly NamedContentData contentData = new NamedContentData();
private readonly NamedContentData contentTransformed = new NamedContentData();
private readonly ClaimsPrincipal user;
@ -60,15 +57,17 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
user = new ClaimsPrincipal(identity);
A.CallTo(() => app.Id).Returns(appId);
A.CallTo(() => app.Name).Returns(appName);
A.CallTo(() => app.Id).Returns(appId.Id);
A.CallTo(() => app.Name).Returns(appId.Name);
A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.English);
A.CallTo(() => schema.AppId).Returns(new NamedId<Guid>(appId, appName));
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.Name).Returns(schemaName);
A.CallTo(() => schema.SchemaDef).Returns(new Schema(schemaName));
A.CallTo(() => schema.ScriptQuery).Returns(script);
var schemaDef =
new Schema(schemaId.Name)
.ConfigureScripts(new Dictionary<string, string> { [Scripts.Query] = "<script-query>" });
A.CallTo(() => schema.Id).Returns(schemaId.Id);
A.CallTo(() => schema.AppId).Returns(appId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
context = QueryContext.Create(app, user);
@ -86,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchema();
var result = await sut.GetSchemaAsync(context, schemaId.ToString());
var result = await sut.GetSchemaAsync(context, schemaId.Name);
Assert.Equal(schema, result);
}
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{
SetupSchema();
var result = await sut.GetSchemaAsync(context, schemaName);
var result = await sut.GetSchemaAsync(context, schemaId.Name);
Assert.Equal(schema, result);
}
@ -108,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context;
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(ctx, schemaName));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetSchemaAsync(ctx, schemaId.Name));
}
[Fact]
@ -118,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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[]
@ -141,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var ctx = context;
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindContentAsync(ctx, schemaId.ToString(), contentId));
await Assert.ThrowsAsync<DomainForbiddenException>(() => sut.FindContentAsync(ctx, schemaId.Name, contentId));
}
[Fact]
@ -155,7 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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]
@ -173,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(content.Id, result.Id);
@ -197,7 +196,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(content.Id, result.Id);
@ -220,7 +219,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(content.Id, result.Id);
@ -250,7 +249,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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]
@ -268,7 +267,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(content.Id, result[0].Id);
@ -294,7 +293,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(contentId, result[0].Id);
@ -314,7 +313,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => modelBuilder.BuildEdmModel(schema, app))
.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[]
@ -348,7 +347,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(total, result.Total);
@ -372,7 +371,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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(total, result.Total);
@ -390,7 +389,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
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)
{
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);
}
}
@ -417,19 +416,19 @@ namespace Squidex.Domain.Apps.Entities.Contents
private void SetupSchema()
{
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))
.Returns(schema);
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns(schema);
}
private void SetupSchemaNotFound()
{
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaName))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Name))
.Returns((ISchemaEntity)null);
A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false))
A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, schemaId.Id, false))
.Returns((ISchemaEntity)null);
}
@ -441,7 +440,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => content.Data).Returns(contentData);
A.CallTo(() => content.DataDraft).Returns(contentData);
A.CallTo(() => content.Status).Returns(status);
A.CallTo(() => content.SchemaId).Returns(new NamedId<Guid>(schemaId, schemaName));
A.CallTo(() => content.SchemaId).Returns(schemaId);
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
{
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 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 IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
@ -76,7 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new StringFieldProperties())
.AddArray(13, "my-array", Partitioning.Invariant, f => f
.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.Name).Returns(appName);
@ -85,10 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
context = QueryContext.Create(app, user);
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.Name).Returns(schemaDef.Name);
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 };

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

@ -8,6 +8,7 @@
using FakeItEasy;
using NodaTime;
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.Guards;
using Squidex.Domain.Apps.Entities.Schemas;
@ -25,6 +26,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanCreate_should_throw_exception_if_data_is_null()
{
SetupSingleton(false);
var command = new CreateContent();
ValidationAssert.Throws(() => GuardContent.CanCreate(schema, command),
@ -34,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanCreate_should_throw_exception_if_singleton()
{
A.CallTo(() => schema.IsSingleton).Returns(true);
SetupSingleton(true);
var command = new CreateContent { Data = new NamedContentData() };
@ -44,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
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 };
@ -54,6 +57,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanCreate_should_not_throw_exception_if_data_is_not_null()
{
SetupSingleton(false);
var command = new CreateContent { Data = new NamedContentData() };
GuardContent.CanCreate(schema, command);
@ -62,6 +67,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanUpdate_should_throw_exception_if_data_is_null()
{
SetupSingleton(false);
var command = new UpdateContent();
ValidationAssert.Throws(() => GuardContent.CanUpdate(command),
@ -71,6 +78,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanUpdate_should_not_throw_exception_if_data_is_not_null()
{
SetupSingleton(false);
var command = new UpdateContent { Data = new NamedContentData() };
GuardContent.CanUpdate(command);
@ -79,6 +88,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanPatch_should_throw_exception_if_data_is_null()
{
SetupSingleton(false);
var command = new PatchContent();
ValidationAssert.Throws(() => GuardContent.CanPatch(command),
@ -88,6 +99,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanPatch_should_not_throw_exception_if_data_is_not_null()
{
SetupSingleton(false);
var command = new PatchContent { Data = new NamedContentData() };
GuardContent.CanPatch(command);
@ -96,6 +109,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_throw_exception_if_status_not_valid()
{
SetupSingleton(false);
var command = new ChangeContentStatus { Status = (Status)10 };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command),
@ -105,6 +120,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_throw_exception_if_status_flow_not_valid()
{
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command),
@ -114,6 +131,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_throw_exception_if_due_date_in_past()
{
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published, DueTime = dueTimeInPast };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command),
@ -123,6 +142,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_throw_exception_if_publishing_without_pending_changes()
{
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published };
ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Published, command),
@ -132,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_throw_exception_if_singleton()
{
A.CallTo(() => schema.IsSingleton).Returns(true);
SetupSingleton(true);
var command = new ChangeContentStatus { Status = Status.Draft };
@ -142,7 +163,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
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 };
@ -152,6 +173,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanChangeContentStatus_should_not_throw_exception_if_status_flow_valid()
{
SetupSingleton(false);
var command = new ChangeContentStatus { Status = Status.Published };
GuardContent.CanChangeContentStatus(schema, false, Status.Draft, command);
@ -168,6 +191,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDiscardChanges_should_not_throw_exception_if_pending()
{
SetupSingleton(false);
var command = new DiscardChanges();
GuardContent.CanDiscardChanges(true, command);
@ -176,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDelete_should_throw_exception_if_singleton()
{
A.CallTo(() => schema.IsSingleton).Returns(true);
SetupSingleton(true);
var command = new DeleteContent();
@ -186,9 +211,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
[Fact]
public void CanDelete_should_not_throw_exception()
{
SetupSingleton(false);
var command = new DeleteContent();
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)
{
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
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "invalid name",
Properties = new StringFieldProperties(),
@ -87,9 +87,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = null,
@ -110,9 +110,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 },
@ -134,9 +134,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = new StringFieldProperties(),
@ -157,15 +157,15 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = new StringFieldProperties(),
Partitioning = Partitioning.Invariant.Key
},
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = new StringFieldProperties(),
@ -186,16 +186,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "array",
Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "invalid name",
Properties = new StringFieldProperties()
@ -217,16 +217,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "array",
Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = null
@ -248,16 +248,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "array",
Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = new ArrayFieldProperties()
@ -279,16 +279,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "array",
Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 }
@ -311,21 +311,21 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "array",
Properties = new ArrayFieldProperties(),
Partitioning = Partitioning.Invariant.Key,
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = new StringFieldProperties()
},
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = new StringFieldProperties()
@ -347,33 +347,33 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards
var command = new CreateSchema
{
AppId = appId,
Fields = new List<CreateSchemaField>
Fields = new List<UpsertSchemaField>
{
new CreateSchemaField
new UpsertSchemaField
{
Name = "field1",
Properties = ValidProperties(),
Partitioning = "invariant"
},
new CreateSchemaField
new UpsertSchemaField
{
Name = "field2",
Properties = ValidProperties(),
Partitioning = "invariant"
},
new CreateSchemaField
new UpsertSchemaField
{
Name = "field3",
Properties = new ArrayFieldProperties(),
Partitioning = "invariant",
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested1",
Properties = ValidProperties()
},
new CreateSchemaNestedField
new UpsertSchemaNestedField
{
Name = "nested2",
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.Threading.Tasks;
using FakeItEasy;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
using Xunit;
@ -18,30 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{
private readonly IStore<Guid> store = A.Fake<IStore<Guid>>();
private readonly IPersistence<SchemasByAppIndexGrain.GrainState> persistence = A.Fake<IPersistence<SchemasByAppIndexGrain.GrainState>>();
private readonly Guid appId = Guid.NewGuid();
private readonly Guid schemaId1 = Guid.NewGuid();
private readonly Guid schemaId2 = Guid.NewGuid();
private readonly string schemaName1 = "my-schema1";
private readonly string schemaName2 = "my-schema2";
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId1 = NamedId.Of(Guid.NewGuid(), "my-schema1");
private readonly NamedId<Guid> schemaId2 = NamedId.Of(Guid.NewGuid(), "my-schema2");
private readonly SchemasByAppIndexGrain sut;
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);
sut = new SchemasByAppIndexGrain(store);
sut.ActivateAsync(appId).Wait();
sut.ActivateAsync(appId.Id).Wait();
}
[Fact]
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))
.MustHaveHappened();
@ -50,10 +49,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
[Fact]
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();
var id = await sut.GetSchemaIdAsync(schemaName1);
var id = await sut.GetSchemaIdAsync(schemaId1.Name);
Assert.Equal(id, Guid.Empty);
@ -64,10 +63,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
[Fact]
public async Task Should_remove_schema_id_from_index()
{
await sut.AddSchemaAsync(schemaId1, schemaName1);
await sut.RemoveSchemaAsync(schemaId1);
await sut.AddSchemaAsync(schemaId1.Id, schemaId1.Name);
await sut.RemoveSchemaAsync(schemaId1.Id);
var result = await sut.GetSchemaIdAsync(schemaName1);
var result = await sut.GetSchemaIdAsync(schemaId1.Name);
Assert.Equal(Guid.Empty, result);
@ -80,16 +79,16 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes
{
var state = new Dictionary<string, Guid>
{
[schemaName1] = schemaId1,
[schemaName2] = schemaId2
[schemaId1.Name] = schemaId1.Id,
[schemaId2.Name] = schemaId2.Id
};
await sut.RebuildAsync(state);
Assert.Equal(schemaId1, await sut.GetSchemaIdAsync(schemaName1));
Assert.Equal(schemaId2, await sut.GetSchemaIdAsync(schemaName2));
Assert.Equal(schemaId1.Id, await sut.GetSchemaIdAsync(schemaId1.Name));
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))
.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>
{
private readonly IAppProvider appProvider = A.Fake<IAppProvider>();
private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry());
private readonly string fieldName = "age";
private readonly string arrayName = "array";
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))
.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();
}
@ -70,13 +69,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas
Assert.Equal(AppId, sut.Snapshot.AppId.Id);
Assert.Equal(SchemaName, sut.Snapshot.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
.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 fields = new List<CreateSchemaField>
var fields = new List<UpsertSchemaField>
{
new CreateSchemaField { Name = "field1", Properties = ValidProperties() },
new CreateSchemaField { Name = "field2", Properties = ValidProperties() },
new CreateSchemaField
new UpsertSchemaField { Name = "field1", Properties = ValidProperties() },
new UpsertSchemaField { Name = "field2", Properties = ValidProperties() },
new UpsertSchemaField
{
Name = "field3",
Partitioning = Partitioning.Language.Key,
Properties = new ArrayFieldProperties(),
Nested = new List<CreateSchemaNestedField>
Nested = new List<UpsertSchemaNestedField>
{
new CreateSchemaNestedField { Name = "nested1", Properties = ValidProperties() },
new CreateSchemaNestedField { Name = "nested2", Properties = ValidProperties() }
new UpsertSchemaNestedField { Name = "nested1", Properties = ValidProperties() },
new UpsertSchemaNestedField { Name = "nested2", Properties = ValidProperties() }
}
}
};
@ -111,10 +110,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
var @event = (SchemaCreated)LastEvents.Single().Payload;
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(3, @event.Fields.Count);
Assert.Equal(3, @event.Schema.Fields.Count);
}
[Fact]
@ -141,11 +140,10 @@ namespace Squidex.Domain.Apps.Entities.Schemas
{
var command = new ConfigureScripts
{
ScriptQuery = "<script-query>",
ScriptCreate = "<script-create>",
ScriptUpdate = "<script-update>",
ScriptDelete = "<script-delete>",
ScriptChange = "<script-change>"
Scripts = new Dictionary<string, string>
{
["Query"] = "<script-query>"
}
};
await ExecuteCreateAsync();
@ -156,14 +154,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
LastEvents
.ShouldHaveSameEvents(
CreateEvent(new ScriptsConfigured
{
ScriptQuery = "<script-query>",
ScriptCreate = "<script-create>",
ScriptUpdate = "<script-update>",
ScriptDelete = "<script-delete>",
ScriptChange = "<script-change>"
})
CreateEvent(new SchemaScriptsConfigured { Scripts = command.Scripts })
);
}
@ -217,7 +208,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(command.Name, sut.Snapshot.Category);
Assert.Equal(command.Name, sut.Snapshot.SchemaDef.Category);
LastEvents
.ShouldHaveSameEvents(
@ -242,7 +233,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas
result.ShouldBeEquivalent(new EntitySavedResult(1));
Assert.Equal(command.PreviewUrls, sut.Snapshot.PreviewUrls);
Assert.Equal(command.PreviewUrls, sut.Snapshot.SchemaDef.PreviewUrls);
LastEvents
.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 ICommandBus commandBus = A.Fake<ICommandBus>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly HttpContext httpContext = new DefaultHttpContext();
private readonly EnrichWithAppIdCommandMiddleware sut;
@ -30,12 +31,21 @@ namespace Squidex.Pipeline.CommandMiddlewares
A.CallTo(() => httpContextAccessor.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);
}
[Fact]
public async Task Should_throw_exception_if_app_not_found()
{
httpContext.Features.Set<IAppFeature>(new AppResolver.AppFeature(null));
var command = new CreateContent();
var context = new CommandContext(command, commandBus);
@ -59,65 +69,45 @@ namespace Squidex.Pipeline.CommandMiddlewares
[Fact]
public async Task Should_assign_app_id_and_name_to_app_command()
{
SetupApp(out var appId, out var appName);
var command = new CreateContent();
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
Assert.Equal(NamedId.Of(appId, appName), command.AppId);
Assert.Equal(appId, command.AppId);
}
[Fact]
public async Task Should_assign_app_id_to_app_self_command()
{
SetupApp(out var appId, out _);
var command = new AddPattern();
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
Assert.Equal(appId, command.AppId);
Assert.Equal(appId.Id, command.AppId);
}
[Fact]
public async Task Should_not_override_app_id()
{
SetupApp(out var appId, out _);
var command = new AddPattern { AppId = Guid.NewGuid() };
var context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
Assert.NotEqual(appId, command.AppId);
Assert.NotEqual(appId.Id, command.AppId);
}
[Fact]
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 context = new CommandContext(command, commandBus);
await sut.HandleAsync(context);
Assert.NotEqual(NamedId.Of(appId, appName), 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));
Assert.NotEqual(appId, command.AppId);
}
}
}

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

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

2
tools/Migrate_01/Migrations/PopulateGrainIndexes.cs

@ -106,7 +106,7 @@ namespace Migrate_01.Migrations
{
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;

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
{
private readonly FieldRegistry fieldRegistry;
private readonly ILocalCache localCache;
private readonly IStore<Guid> store;
private readonly IEventStore eventStore;
public Rebuilder(
FieldRegistry fieldRegistry,
ILocalCache localCache,
IStore<Guid> store,
IEventStore eventStore)
{
this.fieldRegistry = fieldRegistry;
this.eventStore = eventStore;
this.localCache = localCache;
this.store = store;
@ -59,7 +56,7 @@ namespace Migrate_01
{
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()

Loading…
Cancel
Save