Browse Source

Components (#710)

* Components

* Code improvements and more tests.

* Fix test (but why?)
pull/712/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
7e7c324372
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs
  2. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs
  3. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  4. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  5. 12
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs
  6. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  7. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  8. 39
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs
  9. 70
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs
  10. 24
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs
  11. 60
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs
  12. 15
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs
  13. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs
  14. 27
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs
  15. 59
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs
  16. 80
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs
  17. 27
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs
  18. 37
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ReferenceUnionGraphType.cs
  19. 90
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs
  20. 17
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs
  21. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs
  22. 1
      backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs
  23. 212
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs
  24. 35
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  25. 164
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  26. 110
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  27. 25
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs
  28. 12
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs
  29. 493
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs
  30. 76
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs
  31. 1884
      frontend/package-lock.json
  32. 94
      frontend/package.json

25
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.CodeAnalysis;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
@ -15,5 +16,29 @@ namespace Squidex.Domain.Apps.Core.Contents
public sealed record Component(string Type, JsonObject Data, Schema Schema)
{
public const string Discriminator = "schemaId";
public static bool IsValid(IJsonValue? value, [MaybeNullWhen(false)] out string discriminator)
{
discriminator = null!;
if (value is not JsonObject obj)
{
return false;
}
if (!obj.TryGetValue<JsonString>(Discriminator, out var type))
{
return false;
}
if (string.IsNullOrWhiteSpace(type.Value))
{
return false;
}
discriminator = type.Value;
return true;
}
}
}

5
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldExtensions.cs

@ -73,6 +73,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
return (withHidden || !field.IsHidden) && !field.RawProperties.IsUIProperty();
}
public static bool IsComponentLike<T>(this T field) where T : IField
{
return field.RawProperties is ComponentFieldProperties || field.RawProperties is ComponentsFieldProperties;
}
public static bool IsUI<T>(this T field) where T : IField
{
return field.RawProperties is UIFieldProperties;

10
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -45,11 +45,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return field.RawProperties.Label.Or(field.TypeName());
}
public static bool IsSingleton(this Schema schema)
{
return schema.Type == SchemaType.Singleton;
}
public static string TypeName(this Schema schema)
{
return schema.Name.ToPascalCase();
@ -65,11 +60,6 @@ namespace Squidex.Domain.Apps.Core.Schemas
return schema.Properties.Label.Or(schema.Name);
}
public static DomainId SingleId(this ReferencesFieldProperties properties)
{
return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : default;
}
public static IEnumerable<RootField> ReferenceFields(this Schema schema)
{
return schema.RootFields(schema.FieldsInReferences);

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -199,7 +199,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
}
private (object? Result, JsonError? Error) ConvertToComponentList(IField<ComponentsFieldProperties> field, IJsonValue value)
private static (object? Result, JsonError? Error) ConvertToComponentList(IField<ComponentsFieldProperties> field, IJsonValue value)
{
if (value is JsonArray array)
{

12
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs

@ -175,17 +175,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
private static bool IsValidComponent(IJsonValue value)
{
if (value is not JsonObject obj)
{
return false;
}
if (!obj.TryGetValue<JsonString>(Component.Discriminator, out var type))
{
return false;
}
return !string.IsNullOrWhiteSpace(type.Value);
return Component.IsValid(value, out _);
}
}
}

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return await model.ExecuteAsync(options);
}
private async Task<GraphQLModel> GetModelAsync(IAppEntity app)
public async Task<GraphQLModel> GetModelAsync(IAppEntity app)
{
var entry = await GetModelEntryAsync(app);
@ -108,4 +108,4 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return $"GraphQLModel_{appId}_{etag}";
}
}
}
}

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

@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private readonly GraphQLSchema schema;
private readonly ISemanticLog log;
public GraphQLSchema Schema => schema;
public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, SharedTypes typeFactory, ISemanticLog log)
{
this.log = log;

39
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Builder.cs

@ -12,6 +12,7 @@ using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
@ -22,11 +23,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
internal sealed class Builder
{
private readonly Dictionary<SchemaInfo, ComponentGraphType> componentTypes = new Dictionary<SchemaInfo, ComponentGraphType>(ReferenceEqualityComparer.Instance);
private readonly Dictionary<SchemaInfo, ContentGraphType> contentTypes = new Dictionary<SchemaInfo, ContentGraphType>(ReferenceEqualityComparer.Instance);
private readonly Dictionary<SchemaInfo, ContentResultGraphType> contentResultTypes = new Dictionary<SchemaInfo, ContentResultGraphType>(ReferenceEqualityComparer.Instance);
private readonly FieldVisitor fieldVisitor;
private readonly FieldInputVisitor fieldInputVisitor;
private readonly PartitionResolver partitionResolver;
private readonly List<SchemaInfo> allSchemas = new List<SchemaInfo>();
public SharedTypes SharedTypes { get; }
@ -48,7 +51,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public GraphQLSchema BuildSchema(IEnumerable<ISchemaEntity> schemas)
{
var schemaInfos = SchemaInfo.Build(schemas).ToList();
// Do not add schema without fields.
allSchemas.AddRange(SchemaInfo.Build(schemas).Where(x => x.Fields.Count > 0));
// Only published normal schemas (not components are used for entities).
var schemaInfos = allSchemas.Where(x => x.Schema.SchemaDef.IsPublished && x.Schema.SchemaDef.Type != SchemaType.Component).ToList();
foreach (var schemaInfo in schemaInfos)
{
@ -65,9 +72,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
newSchema.RegisterType(SharedTypes.ContentInterface);
if (schemas.Any())
if (schemaInfos.Any())
{
newSchema.Mutation = new AppMutationsGraphType(this, schemaInfos);
var mutations = new AppMutationsGraphType(this, schemaInfos);
if (mutations.Fields.Count > 0)
{
newSchema.Mutation = mutations;
}
}
foreach (var (schemaInfo, contentType) in contentTypes)
@ -75,6 +87,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
contentType.Initialize(this, schemaInfo, schemaInfos);
}
foreach (var (schemaInfo, componentType) in componentTypes)
{
componentType.Initialize(this, schemaInfo);
}
foreach (var contentType in contentTypes.Values)
{
newSchema.RegisterType(contentType);
@ -100,6 +117,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return fieldInfo.Field.Accept(fieldVisitor, fieldInfo);
}
public IObjectGraphType GetContentResultType(SchemaInfo schemaId)
{
return contentResultTypes.GetOrDefault(schemaId);
}
public IObjectGraphType? GetContentType(DomainId schemaId)
{
return contentTypes.FirstOrDefault(x => x.Key.Schema.Id == schemaId).Value;
@ -110,9 +132,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return contentTypes.GetOrDefault(schemaId);
}
public IObjectGraphType GetContentResultType(SchemaInfo schemaId)
public IObjectGraphType? GetComponentType(DomainId schemaId)
{
return contentResultTypes.GetOrDefault(schemaId);
var schema = allSchemas.Find(x => x.Schema.Id == schemaId);
if (schema == null)
{
return null;
}
return componentTypes.GetOrAdd(schema, x => new ComponentGraphType(this, schema));
}
public IEnumerable<KeyValuePair<SchemaInfo, ContentGraphType>> GetAllContentTypes()

70
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs

@ -0,0 +1,70 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentGraphType : ObjectGraphType<JsonObject>
{
public ComponentGraphType(Builder builder, SchemaInfo schemaInfo)
{
Name = schemaInfo.ComponentType;
Description = $"The structure of the {schemaInfo.DisplayName} component schema.";
AddField(ContentFields.SchemaId);
AddResolvedInterface(builder.SharedTypes.ComponentInterface);
IsTypeOf = CheckType(schemaInfo.Schema.Id.ToString());
}
public void Initialize(Builder builder, SchemaInfo schemaInfo)
{
foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.IsComponentLike())
{
AddField(new FieldType
{
Name = fieldInfo.FieldNameDynamic,
Arguments = ContentActions.Json.Arguments,
ResolvedType = AllTypes.Json,
Resolver = FieldVisitor.JsonPath,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
}
}
private static Func<object, bool> CheckType(string schemaId)
{
return value =>
{
return Component.IsValid(value as IJsonValue, out var discrimiator) && discrimiator == schemaId.ToString();
};
}
}
}

24
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentInterfaceGraphType.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentInterfaceGraphType : InterfaceGraphType<JsonObject>
{
public ComponentInterfaceGraphType()
{
Name = "Component";
AddField(ContentFields.SchemaId);
Description = "The structure of all content types.";
}
}
}

60
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs

@ -0,0 +1,60 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ComponentUnionGraphType : UnionGraphType
{
private readonly Dictionary<string, IObjectGraphType> types = new Dictionary<string, IObjectGraphType>();
public bool HasType => types.Count > 0;
public ComponentUnionGraphType(Builder builder, FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
Name = fieldInfo.ReferenceType;
if (schemaIds?.Any() == true)
{
foreach (var schemaId in schemaIds)
{
var contentType = builder.GetComponentType(schemaId);
if (contentType != null)
{
types[schemaId.ToString()] = contentType;
}
}
}
if (HasType)
{
foreach (var type in types)
{
AddPossibleType(type.Value);
}
ResolveType = value =>
{
if (value is JsonObject component && component.TryGetValue<JsonString>(Component.Discriminator, out var schemaId))
{
return types.GetOrDefault(schemaId.Value);
}
return null;
};
}
}
}
}

15
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs

@ -8,7 +8,9 @@
using System;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
@ -94,6 +96,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = "The color status of the content."
};
public static readonly FieldType SchemaId = new FieldType
{
Name = "schemaId",
ResolvedType = AllTypes.NonNullString,
Resolver = Resolve(x => x[Component.Discriminator].ToString()),
Description = "The id of the schema."
};
private static IFieldResolver Resolve<T>(Func<JsonObject, T> resolver)
{
return Resolvers.Sync(resolver);
}
private static IFieldResolver Resolve<T>(Func<IEnrichedContentEntity, T> resolver)
{
return Resolvers.Sync(resolver);

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using GraphQL.Types;
@ -33,7 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
AddField(ContentFields.LastModifiedByUser);
AddField(ContentFields.Status);
AddField(ContentFields.StatusColor);
AddResolvedInterface(builder.SharedTypes.ContentInterface);
Description = $"The structure of a {schemaInfo.DisplayName} content type.";
@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private bool CheckType(object value)
{
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
return value is IContentEntity content && content.SchemaId?.Id == schemaId;
}
public void Initialize(Builder builder, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> allSchemas)

27
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs

@ -19,8 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
if (fieldInfo.Field.IsComponentLike())
{
AddField(new FieldType
{
@ -31,21 +30,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
{
AddField(new FieldType
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
}

59
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs

@ -20,16 +20,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var fieldInfo in schemaInfo.Fields)
{
if (fieldInfo.Field.RawProperties is ComponentFieldProperties ||
fieldInfo.Field.RawProperties is ComponentsFieldProperties)
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
if (fieldInfo.Field.IsComponentLike())
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
foreach (var partitionKey in partitioning.AllKeys)
{
fieldGraphType.AddField(new FieldType
@ -51,40 +50,36 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
if (resolver != null)
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
var (resolvedType, resolver, args) = builder.GetGraphType(fieldInfo);
var partitioning = builder.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
if (resolver != null)
{
var fieldGraphType = new ObjectGraphType
{
Name = fieldInfo.LocalizedType
};
foreach (var partitionKey in partitioning.AllKeys)
foreach (var partitionKey in partitioning.AllKeys)
{
fieldGraphType.AddField(new FieldType
{
fieldGraphType.AddField(new FieldType
{
Name = partitionKey.EscapePartition(),
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey);
}
Name = partitionKey.EscapePartition(),
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey);
}
fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType
{
Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
AddField(new FieldType
{
Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field
}).WithSourceName(fieldInfo);
}
}

80
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs

@ -12,6 +12,8 @@ using GraphQL;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
@ -102,12 +104,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default;
}
var schemaFieldType =
new ListGraphType(
new NonNullGraphType(
new NestedGraphType(builder, args)));
var type = new NestedGraphType(builder, args);
return (schemaFieldType, JsonNoop, null);
if (type.Fields.Count == 0)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<AssetsFieldProperties> field, FieldInfo args)
@ -122,12 +126,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentFieldProperties> field, FieldInfo args)
{
return (AllTypes.Json, JsonPath, ContentActions.Json.Arguments);
var type = ResolveComponent(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (type, JsonNoop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ComponentsFieldProperties> field, FieldInfo args)
{
return (AllTypes.Json, JsonPath, ContentActions.Json.Arguments);
var type = ResolveComponent(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<DateTimeFieldProperties> field, FieldInfo args)
@ -162,7 +180,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ReferencesFieldProperties> field, FieldInfo args)
{
return ResolveReferences(field, args);
var type = ResolveReferences(args, field.Properties.SchemaIds);
if (type == null)
{
return default;
}
return (new ListGraphType(new NonNullGraphType(type)), References, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<UIFieldProperties> field, FieldInfo args)
@ -170,15 +195,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default;
}
private (IGraphType?, IFieldResolver?, QueryArguments?) ResolveReferences(IField<ReferencesFieldProperties> field, FieldInfo args)
private IGraphType? ResolveReferences(FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
IGraphType? contentType = builder.GetContentType(field.Properties.SingleId());
IGraphType? contentType = null;
if (schemaIds?.Count == 1)
{
contentType = builder.GetContentType(schemaIds[0]);
}
if (contentType == null)
{
var union = new ContentUnionGraphType(builder, args, field.Properties);
var union = new ReferenceUnionGraphType(builder, fieldInfo, schemaIds);
if (!union.PossibleTypes.Any())
if (!union.HasType)
{
return default;
}
@ -186,9 +216,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
contentType = union;
}
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType));
return contentType;
}
private IGraphType? ResolveComponent(FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
IGraphType? componentType = null;
if (schemaIds?.Count == 1)
{
componentType = builder.GetComponentType(schemaIds[0]);
}
if (componentType == null)
{
var union = new ComponentUnionGraphType(builder, fieldInfo, schemaIds);
if (!union.HasType)
{
return default;
}
componentType = union;
}
return (schemaFieldType, References, null);
return componentType;
}
private static IFieldResolver CreateValueResolver(ValueResolver valueResolver)

27
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs

@ -19,8 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var nestedFieldInfo in fieldInfo.Fields)
{
if (nestedFieldInfo.Field.RawProperties is ComponentFieldProperties ||
nestedFieldInfo.Field.RawProperties is ComponentsFieldProperties)
if (nestedFieldInfo.Field.IsComponentLike())
{
AddField(new FieldType
{
@ -31,21 +30,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
else
{
var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null)
var (resolvedType, resolver, args) = builder.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
Name = nestedFieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = nestedFieldInfo.Field.RawProperties.Hints
}).WithSourceName(nestedFieldInfo);
}
}

37
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ReferenceUnionGraphType.cs

@ -8,22 +8,24 @@
using System.Collections.Generic;
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class ContentUnionGraphType : UnionGraphType
internal sealed class ReferenceUnionGraphType : UnionGraphType
{
private readonly Dictionary<DomainId, IObjectGraphType> types = new Dictionary<DomainId, IObjectGraphType>();
public ContentUnionGraphType(Builder builder, FieldInfo fieldInfo, ReferencesFieldProperties properties)
public bool HasType => types.Count > 0;
public ReferenceUnionGraphType(Builder builder, FieldInfo fieldInfo, ImmutableList<DomainId>? schemaIds)
{
Name = fieldInfo.UnionType;
Name = fieldInfo.ReferenceType;
if (properties.SchemaIds?.Any() == true)
if (schemaIds?.Any() == true)
{
foreach (var schemaId in properties.SchemaIds)
foreach (var schemaId in schemaIds)
{
var contentType = builder.GetContentType(schemaId);
@ -41,20 +43,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}
}
foreach (var type in types)
if (HasType)
{
AddPossibleType(type.Value);
}
ResolveType = value =>
{
if (value is IContentEntity content)
foreach (var type in types)
{
return types.GetOrDefault(content.SchemaId.Id);
AddPossibleType(type.Value);
}
return null;
};
ResolveType = value =>
{
if (value is IContentEntity content)
{
return types.GetOrDefault(content.SchemaId.Id);
}
return null;
};
}
}
}
}

90
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/SchemaInfo.cs

@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public string DisplayName { get; }
public string ComponentType { get; }
public string ContentType { get; }
public string DataType { get; }
@ -38,6 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private SchemaInfo(ISchemaEntity schema, string typeName, IReadOnlyList<FieldInfo> fields, Names names)
{
Schema = schema;
ComponentType = names[$"{typeName}Component"];
ContentType = names[typeName];
DataFlatType = names[$"{typeName}FlatDataDto"];
DataInputType = names[$"{typeName}DataInputDto"];
@ -57,37 +60,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
var names = new Names();
var validSchemas = schemas.Where(x =>
x.SchemaDef.IsPublished &&
x.SchemaDef.Type != SchemaType.Component &&
x.SchemaDef.Fields.Count > 0);
foreach (var schema in validSchemas.OrderBy(x => x.Created))
foreach (var schema in schemas.OrderBy(x => x.Created))
{
var typeName = schema.TypeName();
var fields = FieldInfo.EmptyFields;
var fieldInfos = new List<FieldInfo>(schema.SchemaDef.Fields.Count);
var fieldNames = new Names();
if (schema.SchemaDef.Fields.Count > 0)
foreach (var field in schema.SchemaDef.Fields.ForApi())
{
var fieldNames = new Names();
fields = new List<FieldInfo>(schema.SchemaDef.Fields.Count);
foreach (var field in schema.SchemaDef.Fields)
{
var fieldName = fieldNames[field];
fields.Add(FieldInfo.Build(
field,
fieldName,
fieldNames[fieldName.AsDynamic()],
names[$"{typeName}Data{field.TypeName()}"],
names));
}
fieldInfos.Add(FieldInfo.Build(
field,
names[$"{typeName}Data{field.TypeName()}"],
names,
fieldNames));
}
yield return new SchemaInfo(schema, typeName, fields, names);
yield return new SchemaInfo(schema, typeName, fieldInfos, names);
}
}
}
@ -112,22 +101,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public string NestedInputType { get; }
public string UnionType { get; }
public string ComponentType { get; }
public string ReferenceType { get; }
public IReadOnlyList<FieldInfo> Fields { get; }
private FieldInfo(IField field, string fieldName, string fieldNameDynamic, string typeName, IReadOnlyList<FieldInfo> fields, Names names)
private FieldInfo(IField field, string typeName, Names names, Names parentNames, IReadOnlyList<FieldInfo> fields)
{
var fieldName = parentNames[field.Name.ToCamelCase(), false];
ComponentType = names[$"{typeName}ComponentUnionDto"];
DisplayName = field.DisplayName();
Field = field;
Fields = fields;
FieldName = fieldName;
FieldNameDynamic = fieldNameDynamic;
FieldNameDynamic = names[$"{fieldName}__Dynamic"];
LocalizedType = names[$"{typeName}Dto"];
LocalizedInputType = names[$"{typeName}InputDto"];
NestedInputType = names[$"{typeName}ChildInputDto"];
NestedType = names[$"{typeName}ChildDto"];
UnionType = names[$"{typeName}UnionDto"];
ReferenceType = names[$"{typeName}UnionDto"];
}
public override string ToString()
@ -135,30 +129,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return FieldName;
}
internal static FieldInfo Build(IRootField rootField, string fieldName, string fieldNameDynamic, string typeName, Names names)
internal static FieldInfo Build(IRootField rootField, string typeName, Names names, Names parentNames)
{
var fields = EmptyFields;
var fieldInfos = EmptyFields;
if (rootField is IArrayField arrayField && arrayField.Fields.Count > 0)
if (rootField is IArrayField arrayField)
{
var nestedNames = new Names();
var fieldNames = new Names();
fields = new List<FieldInfo>(arrayField.Fields.Count);
fieldInfos = new List<FieldInfo>(arrayField.Fields.Count);
foreach (var field in arrayField.Fields)
foreach (var nestedField in arrayField.Fields.ForApi())
{
var nestedName = nestedNames[field];
fields.Add(new FieldInfo(field,
nestedName,
nestedNames[nestedName.AsDynamic()],
$"{typeName}{field.TypeName()}",
EmptyFields,
names));
fieldInfos.Add(new FieldInfo(
nestedField,
names[$"{typeName}{nestedField.TypeName()}"],
names,
fieldNames,
EmptyFields));
}
}
return new FieldInfo(rootField, fieldName, fieldNameDynamic, typeName, fields, names);
return new FieldInfo(rootField, typeName, names, parentNames, fieldInfos);
}
}
@ -170,6 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
"Asset",
"AssetResultDto",
"Content",
"Component",
"EntityCreatedResultDto",
"EntitySavedResultDto",
"JsonScalar",
@ -178,14 +171,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
};
private readonly Dictionary<string, int> takenNames = new Dictionary<string, int>();
public string this[IField field]
{
get => GetName(field.Name.ToCamelCase(), false);
}
public string this[string name]
public string this[string name, bool isEntity = true]
{
get => GetName(name, true);
get => GetName(name, isEntity);
}
private string GetName(string name, bool isEntity)
@ -196,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
name = "gql_" + name;
}
else if (ReservedNames.Contains(name) && isEntity)
else if (isEntity && ReservedNames.Contains(name))
{
name = $"{name}Entity";
}

17
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs

@ -11,26 +11,11 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public static class Extensions
{
public static string AsDynamic(this string value, bool input = false)
{
#pragma warning disable RECS0015 // If an extension method is called as static method convert it to method syntax
var result = CasingExtensions.ToCamelCase(value);
#pragma warning restore RECS0015 // If an extension method is called as static method convert it to method syntax
if (!input)
{
result = $"{result}__Dynamic";
}
return result;
}
internal static string BuildODataQuery(this IResolveFieldContext context)
{
var sb = DefaultPools.StringBuilder.Get();
@ -111,7 +96,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return type.ResolvedType.Flatten();
}
internal static IGraphType Flatten(this IGraphType type)
public static IGraphType Flatten(this IGraphType type)
{
if (type is IProvideResolvedType provider)
{

8
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs

@ -19,6 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
private readonly Lazy<IGraphType> assetsList;
private readonly Lazy<IGraphType> assetsResult;
private readonly Lazy<IInterfaceGraphType> contentInterface;
private readonly Lazy<IInterfaceGraphType> componentInterface;
private readonly Lazy<FieldType> findAsset;
private readonly Lazy<FieldType> queryAssets;
private readonly Lazy<FieldType> queryAssetsWithTotal;
@ -31,6 +32,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public IInterfaceGraphType ContentInterface => contentInterface.Value;
public IInterfaceGraphType ComponentInterface => componentInterface.Value;
public FieldType FindAsset => findAsset.Value;
public FieldType QueryAssets => queryAssets.Value;
@ -59,6 +62,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
return new ContentInterfaceGraphType();
});
componentInterface = new Lazy<IInterfaceGraphType>(() =>
{
return new ComponentInterfaceGraphType();
});
findAsset = new Lazy<FieldType>(() =>
{
return new FieldType

1
backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs

@ -7,7 +7,6 @@
using System.Collections.Generic;
using Squidex.Areas.Api.Controllers.Schemas.Models.Converters;
using Squidex.Areas.Api.Controllers.Schemas.Models.Fields;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Validation;

212
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLIntrospectionTests.cs

@ -5,9 +5,17 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.Types;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Xunit;
using Schema = Squidex.Domain.Apps.Core.Schemas.Schema;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
@ -19,9 +27,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
const string query = @"
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
@ -74,7 +88,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
type {
...TypeRef
}
defaultValue
}
@ -101,5 +117,193 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
Assert.NotEmpty(json);
}
[Fact]
public async Task Should_create_empty_schema()
{
var model = await CreateSut().GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish());
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema_because_ui_field()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddUI(1, "ui", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_empty_schema_because_invalid_field()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddComponent(1, "component", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_empty_schema_with_unpublished_schema()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content")
.AddString(1, "myField", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.NotNull(model);
}
[Fact]
public async Task Should_create_schema_with_reserved_schema_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "content"),
new Schema("content").Publish()
.AddString(1, "myField", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
Assert.Contains(model.Schema.AllTypes, x => x.Name == "ContentEntity");
}
[Fact]
public async Task Should_create_schema_with_reserved_field_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "content", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "content");
}
[Fact]
public async Task Should_create_schema_with_invalid_field_name()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "2-field", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "gql_2Field");
}
[Fact]
public async Task Should_create_schema_with_duplicate_field_names()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddString(1, "my-field", Partitioning.Invariant)
.AddString(2, "my_field", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.Contains(type.Fields, x => x.Name == "myField");
Assert.Contains(type.Fields, x => x.Name == "myField2");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_component()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddComponent(1, "my-component", Partitioning.Invariant,
new ComponentFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myComponent");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_components()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddComponents(1, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myComponents");
}
[Fact]
public async Task Should_not_create_schema_With_invalid_references()
{
var schema =
Mocks.Schema(TestApp.DefaultId,
NamedId.Of(DomainId.NewGuid(), "my-schema"),
new Schema("my-schema").Publish()
.AddReferences(1, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() })
.AddString(2, "my-string", Partitioning.Invariant));
var model = await CreateSut(schema).GetModelAsync(TestApp.Default);
var type = FindDataType(model, "MySchema");
Assert.DoesNotContain(type.Fields, x => x.Name == "myReferences");
}
private static IObjectGraphType FindDataType(GraphQLModel model, string schema)
{
var type = (IObjectGraphType)model.Schema.AllTypes.Single(x => x.Name == schema);
return (IObjectGraphType)type.GetField("flatData").ResolvedType.Flatten();
}
}
}

35
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public GraphQLMutationTests()
{
content = TestContent.Create(appId, schemaId, contentId, schemaRefId1.Id, schemaRefId2.Id, null);
content = TestContent.Create(contentId, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id, null);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>.Ignored))
.Returns(commandContext);
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => commandBus.PublishAsync(
A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any &&
x.ContentId == DomainId.Create("123") &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
@ -172,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => commandBus.PublishAsync(
A<CreateContent>.That.Matches(x =>
x.ExpectedVersion == EtagVersion.Any &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
@ -248,7 +248,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpdateContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
}
@ -281,7 +281,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpdateContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
}
@ -356,7 +356,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpsertContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
@ -390,7 +390,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<UpsertContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
@ -466,7 +466,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<PatchContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
}
@ -499,7 +499,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<PatchContent>.That.Matches(x =>
x.ContentId == content.Id &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Data.Equals(content.Data))))
.MustHaveHappened();
}
@ -577,7 +577,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId &&
x.DueTime == dueTime &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published)))
.MustHaveHappened();
}
@ -611,7 +611,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId &&
x.DueTime == null &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published)))
.MustHaveHappened();
}
@ -645,7 +645,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
x.ContentId == contentId &&
x.DueTime == null &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId) &&
x.SchemaId.Equals(TestSchemas.DefaultId) &&
x.Status == Status.Published)))
.MustHaveHappened();
}
@ -723,20 +723,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A<DeleteContent>.That.Matches(x =>
x.ContentId == contentId &&
x.ExpectedVersion == 10 &&
x.SchemaId.Equals(schemaId))))
x.SchemaId.Equals(TestSchemas.DefaultId))))
.MustHaveHappened();
}
private string CreateQuery(string query)
{
query = query
.Replace("'", "\"")
.Replace("<ID>", contentId.ToString())
.Replace("'", "\"")
.Replace("`", "\"")
.Replace("<FIELDS>", TestContent.AllFields);
if (query.Contains("<DATA>"))
{
var data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id);
var data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id);
var dataJson = JsonConvert.SerializeObject(data, Formatting.Indented);
var dataString = Regex.Replace(dataJson, "\"([^\"]+)\":", x => x.Groups[1].Value + ":").Replace(".0", string.Empty);
@ -751,7 +752,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
var input = new
{
data = TestContent.Data(content, true, schemaRefId1.Id, schemaRefId2.Id)
data = TestContent.Input(content, TestSchemas.Ref1.Id, TestSchemas.Ref2.Id)
};
return JObject.FromObject(input).ToInputs();

164
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs

@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}");
var asset = TestAsset.Create(appId, DomainId.NewGuid());
var asset = TestAsset.Create(DomainId.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == true), A<CancellationToken>._))
@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
data = new
{
queryAssets = new dynamic[]
queryAssets = new[]
{
TestAsset.Response(asset)
}
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}");
var asset = TestAsset.Create(appId, DomainId.NewGuid());
var asset = TestAsset.Create(DomainId.NewGuid());
A.CallTo(() => assetQuery.QueryAsync(MatchsAssetContext(), null,
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5&$filter=my-query" && x.NoTotal == false), A<CancellationToken>._))
@ -108,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
queryAssetsWithTotal = new
{
total = 10,
items = new dynamic[]
items = new[]
{
TestAsset.Response(asset)
}
@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_asset_if_finding_asset()
{
var assetId = DomainId.NewGuid();
var asset = TestAsset.Create(appId, assetId);
var asset = TestAsset.Create(assetId);
var query = CreateQuery(@"
query {
@ -182,36 +182,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var query = CreateQuery(@"
query {
queryMySchemaContents(top: 30, skip: 5) {
id
version
created
createdBy
lastModified
lastModifiedBy
status
statusColor
url
flatData {
myString
myNumber
myBoolean
myDatetime
myJsonValue: myJson(path: 'value')
myJson
myGeolocation
myTags
myLocalized
myArray {
nestedNumber
nestedBoolean
}
}
<FIELDS_CONTENT_FLAT>
}
}");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty);
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content));
@ -221,56 +199,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
data = new
{
queryMySchemaContents = new dynamic[]
queryMySchemaContents = new[]
{
new
{
id = content.Id,
version = 1,
created = content.Created,
createdBy = "subject:user1",
lastModified = content.LastModified,
lastModifiedBy = "client:client1",
status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}",
flatData = new
{
myString = "value",
myNumber = 1.0,
myBoolean = true,
myDatetime = content.LastModified.ToString(),
myJsonValue = 1,
myJson = new
{
value = 1
},
myGeolocation = new
{
latitude = 10,
longitude = 20
},
myTags = new[]
{
"tag1",
"tag2"
},
myLocalized = "de-DE",
myArray = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
}
}
TestContent.FlatResponse(content)
}
}
};
@ -288,9 +219,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty);
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == true), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(0, content));
@ -300,7 +232,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
data = new
{
queryMySchemaContents = new dynamic[]
queryMySchemaContents = new[]
{
TestContent.Response(content)
}
@ -323,9 +255,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}");
var content = TestContent.Create(appId, schemaId, DomainId.NewGuid(), DomainId.Empty, DomainId.Empty);
var contentId = DomainId.NewGuid();
var content = TestContent.Create(contentId);
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), schemaId.Id.ToString(),
A.CallTo(() => contentQuery.QueryAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(),
A<Q>.That.Matches(x => x.ODataQuery == "?$top=30&$skip=5" && x.NoTotal == false), A<CancellationToken>._))
.Returns(ResultList.CreateFrom(10, content));
@ -338,7 +271,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
queryMySchemaContentsWithTotal = new
{
total = 10,
items = new dynamic[]
items = new[]
{
TestContent.Response(content)
}
@ -381,7 +314,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_content_if_finding_content()
{
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty);
var content = TestContent.Create(contentId);
var query = CreateQuery(@"
query {
@ -410,7 +343,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_return_single_content_if_finding_content_with_version()
{
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty);
var content = TestContent.Create(contentId);
var query = CreateQuery(@"
query {
@ -419,7 +352,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
}", contentId);
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), schemaId.Id.ToString(), contentId, 3, A<CancellationToken>._))
A.CallTo(() => contentQuery.FindAsync(MatchsContentContext(), TestSchemas.Default.Id.ToString(), contentId, 3, A<CancellationToken>._))
.Returns(content);
var result = await ExecuteAsync(new ExecutionOptions { Query = query });
@ -439,10 +372,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referenced_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1");
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty);
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
@ -453,7 +386,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
iv {
id
data {
ref1Field {
schemaRef1Field {
iv
}
}
@ -489,7 +422,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId,
data = new
{
ref1Field = new
schemaRef1Field = new
{
iv = "ref1"
}
@ -509,10 +442,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referencing_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1");
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty);
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
@ -521,8 +454,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
referencingMySchemaContents(top: 30, skip: 5) {
id
data {
myString {
de
myLocalizedString {
de_DE
}
}
}
@ -552,9 +485,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentId,
data = new
{
myString = new
myLocalizedString = new
{
de = "value"
de_DE = "de-DE"
}
}
}
@ -570,10 +503,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referencing_contents_with_total_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1");
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "ref1-field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty);
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
@ -584,8 +517,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
items {
id
data {
myString {
de
myLocalizedString {
de_DE
}
}
}
@ -619,9 +552,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentId,
data = new
{
myString = new
myLocalizedString = new
{
de = "value"
de_DE = "de-DE"
}
}
}
@ -638,10 +571,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_union_contents_if_field_is_included_in_query()
{
var contentRefId = DomainId.NewGuid();
var contentRef = TestContent.CreateRef(schemaRefId1, contentRefId, "ref1-field", "ref1");
var contentRef = TestContent.CreateRef(TestSchemas.Ref1Id, contentRefId, "schemaRef1Field", "ref1");
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, contentRefId, DomainId.Empty);
var content = TestContent.Create(contentId, contentRefId);
var query = CreateQuery(@"
query {
@ -655,7 +588,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
... on MyRefSchema1 {
data {
ref1Field {
schemaRef1Field {
iv
}
}
@ -693,7 +626,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
id = contentRefId,
data = new
{
ref1Field = new
schemaRef1Field = new
{
iv = "ref1"
}
@ -714,10 +647,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_also_fetch_referenced_assets_if_field_is_included_in_query()
{
var assetRefId = DomainId.NewGuid();
var assetRef = TestAsset.Create(appId, assetRefId);
var assetRef = TestAsset.Create(assetRefId);
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, assetRefId);
var content = TestContent.Create(contentId, assetId: assetRefId);
var query = CreateQuery(@"
query {
@ -772,7 +705,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public async Task Should_not_return_data_if_field_not_part_of_content()
{
var contentId = DomainId.NewGuid();
var content = TestContent.Create(appId, schemaId, contentId, DomainId.Empty, DomainId.Empty, new ContentData());
var content = TestContent.Create(contentId, data: new ContentData());
var query = CreateQuery(@"
query {
@ -808,13 +741,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
.Replace("'", "\"")
.Replace("<ID>", id.ToString())
.Replace("<FIELDS_ASSET>", TestAsset.AllFields)
.Replace("<FIELDS_CONTENT>", TestContent.AllFields);
.Replace("<FIELDS_CONTENT>", TestContent.AllFields)
.Replace("<FIELDS_CONTENT_FLAT>", TestContent.AllFlatFields);
}
private Context MatchsAssetContext()
{
return A<Context>.That.Matches(x =>
x.App == app &&
x.App == TestApp.Default &&
x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() &&
x.User == requestContext.User);
@ -823,7 +757,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private Context MatchsContentContext()
{
return A<Context>.That.Matches(x =>
x.App == app &&
x.App == TestApp.Default &&
x.ShouldSkipCleanup() &&
x.ShouldSkipContentEnrichment() &&
x.User == requestContext.User);

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

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FakeItEasy;
@ -19,9 +18,7 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Squidex.Caching;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
@ -49,23 +46,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
protected readonly ICommandBus commandBus = A.Fake<ICommandBus>();
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly IUserResolver userResolver = A.Fake<IUserResolver>();
protected readonly ISchemaEntity schema;
protected readonly ISchemaEntity schemaRef1;
protected readonly ISchemaEntity schemaRef2;
protected readonly ISchemaEntity schemaInvalidName;
protected readonly IAppEntity app;
protected readonly Context requestContext;
protected readonly NamedId<DomainId> appId = NamedId.Of(DomainId.NewGuid(), "my-app");
protected readonly NamedId<DomainId> schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema");
protected readonly NamedId<DomainId> schemaRefId1 = NamedId.Of(DomainId.NewGuid(), "my-ref-schema1");
protected readonly NamedId<DomainId> schemaRefId2 = NamedId.Of(DomainId.NewGuid(), "my-ref-schema2");
protected readonly NamedId<DomainId> schemaInvalidNameId = NamedId.Of(DomainId.NewGuid(), "content");
protected readonly CachingGraphQLService sut;
public GraphQLTestBase()
{
app = Mocks.App(appId, Language.DE, Language.GermanGermany);
A.CallTo(() => userResolver.QueryManyAsync(A<string[]>._))
.ReturnsLazily(x =>
{
@ -76,81 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return Task.FromResult(users.ToDictionary(x => x.Id));
});
var schemaDef =
new Schema(schemaId.Name)
.Publish()
.AddJson(1, "my-json", Partitioning.Invariant,
new JsonFieldProperties())
.AddString(2, "my-string", Partitioning.Language,
new StringFieldProperties())
.AddString(3, "my-string2", Partitioning.Invariant,
new StringFieldProperties())
.AddString(4, "my-localized", Partitioning.Language,
new StringFieldProperties())
.AddNumber(5, "my-number", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(6, "my_number", Partitioning.Invariant,
new NumberFieldProperties())
.AddAssets(7, "my-assets", Partitioning.Invariant,
new AssetsFieldProperties())
.AddBoolean(8, "my-boolean", Partitioning.Invariant,
new BooleanFieldProperties())
.AddDateTime(9, "my-datetime", Partitioning.Invariant,
new DateTimeFieldProperties())
.AddReferences(10, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = schemaRefId1.Id })
.AddReferences(11, "my-union", Partitioning.Invariant,
new ReferencesFieldProperties())
.AddReferences(12, "my-invalid", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = DomainId.NewGuid() })
.AddGeolocation(13, "my-geolocation", Partitioning.Invariant,
new GeolocationFieldProperties())
.AddComponent(14, "my-component", Partitioning.Invariant,
new ComponentFieldProperties())
.AddComponents(15, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties())
.AddTags(16, "my-tags", Partitioning.Invariant,
new TagsFieldProperties())
.AddArray(17, "my-empty-array", Partitioning.Invariant, null,
new ArrayFieldProperties())
.AddNumber(50, "2_numbers", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(51, "2-numbers", Partitioning.Invariant,
new NumberFieldProperties())
.AddNumber(52, "content", Partitioning.Invariant,
new NumberFieldProperties())
.AddArray(100, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean")
.AddNumber(122, "nested-number")
.AddNumber(123, "nested_number"))
.SetScripts(new SchemaScripts { Query = "<query-script>" });
schema = Mocks.Schema(appId, schemaId, schemaDef);
var schemaRef1Def =
new Schema(schemaRefId1.Name)
.Publish()
.AddString(1, "ref1-field", Partitioning.Invariant);
schemaRef1 = Mocks.Schema(appId, schemaRefId1, schemaRef1Def);
var schemaRef2Def =
new Schema(schemaRefId2.Name)
.Publish()
.AddString(1, "ref2-field", Partitioning.Invariant);
schemaRef2 = Mocks.Schema(appId, schemaRefId2, schemaRef2Def);
var schemaInvalidNameDef =
new Schema(schemaInvalidNameId.Name)
.Publish()
.AddString(1, "my-field", Partitioning.Invariant);
schemaInvalidName = Mocks.Schema(appId, schemaInvalidNameId, schemaInvalidNameDef);
requestContext = new Context(Mocks.FrontendUser(), app);
sut = CreateSut();
requestContext = new Context(Mocks.FrontendUser(), TestApp.Default);
}
protected void AssertResult(object expected, ExecutionResult result)
@ -167,9 +77,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
if (permissionId != null)
{
var permission = Permissions.ForApp(permissionId, app.Name, schemaId.Name).Id;
var permission = Permissions.ForApp(permissionId, TestApp.Default.Name, TestSchemas.DefaultId.Name).Id;
context = new Context(Mocks.FrontendUser(permission: permission), app);
context = new Context(Mocks.FrontendUser(permission: permission), TestApp.Default);
}
return ExcecuteAsync(options, context);
@ -177,6 +87,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private Task<ExecutionResult> ExcecuteAsync(ExecutionOptions options, Context context)
{
var sut = CreateSut(TestSchemas.Default, TestSchemas.Ref1, TestSchemas.Ref2);
options.UserContext = ActivatorUtilities.CreateInstance<GraphQLExecutionContext>(sut.Services, context);
var listener = sut.Services.GetService<DataLoaderDocumentListener>();
@ -189,20 +101,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return sut.ExecuteAsync(options);
}
private CachingGraphQLService CreateSut()
protected CachingGraphQLService CreateSut(params ISchemaEntity[] schemas)
{
var cache = new BackgroundCache(new MemoryCache(Options.Create(new MemoryCacheOptions())));
var appProvider = A.Fake<IAppProvider>();
A.CallTo(() => appProvider.GetSchemasAsync(appId.Id))
.Returns(new List<ISchemaEntity>
{
schema,
schemaRef1,
schemaRef2,
schemaInvalidName
});
A.CallTo(() => appProvider.GetSchemasAsync(TestApp.Default.Id))
.Returns(schemas.ToList());
var dataLoaderContext = (IDataLoaderContextAccessor)new DataLoaderContextAccessor();
var dataLoaderListener = new DataLoaderDocumentListener(dataLoaderContext);

25
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestApp.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public static class TestApp
{
public static readonly NamedId<DomainId> DefaultId = NamedId.Of(DomainId.NewGuid(), "my-app");
public static readonly IAppEntity Default;
static TestApp()
{
Default = Mocks.App(DefaultId, Language.DE, Language.GermanGermany);
}
}
}

12
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestAsset.cs

@ -21,15 +21,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
created
createdBy
createdByUser {
id,
email,
id
email
displayName
}
lastModified
lastModifiedBy
lastModifiedByUser {
id,
email,
id
email
displayName
}
url
@ -52,14 +52,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
metadata
slug";
public static IEnrichedAssetEntity Create(NamedId<DomainId> appId, DomainId id)
public static IEnrichedAssetEntity Create(DomainId id)
{
var now = SystemClock.Instance.GetCurrentInstant();
var asset = new AssetEntity
{
Id = id,
AppId = appId,
AppId = TestApp.DefaultId,
Version = 1,
Created = now,
CreatedBy = RefToken.User("user1"),

493
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

@ -8,10 +8,8 @@
using System.Collections.Generic;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
@ -23,105 +21,150 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
created
createdBy
createdByUser {
id,
email,
id
email
displayName
}
lastModified
lastModifiedBy
lastModifiedByUser {
id,
email,
id
email
displayName
}
status
statusColor
url
data {
gql_2Numbers {
iv
myJson {
iv
ivValue: iv(path: ""value"")
}
myString {
iv
}
myLocalizedString {
de_DE
}
myNumber {
iv
}
myBoolean {
iv
}
myDatetime {
iv
}
myGeolocation {
iv
}
myComponent__Dynamic {
iv
}
myComponent {
iv {
schemaId
schemaRef1Field
}
gql_2Numbers2 {
iv
}
myComponents__Dynamic {
iv
}
myComponents {
iv {
__typename
... on MyRefSchema1Component {
schemaId
schemaRef1Field
}
... on MyRefSchema2Component {
schemaId
schemaRef2Field
}
}
content {
iv
}
myTags {
iv
}
myArray {
iv {
nestedNumber
nestedBoolean
}
myString {
de
}
myString2 {
iv
}
myNumber {
iv
}
myNumber2 {
iv
}
myBoolean {
iv
}
myDatetime {
iv
}
myJson {
iv
}
myGeolocation {
iv
}
myComponent__Dynamic {
iv
}
myComponents__Dynamic {
iv
}
myTags {
iv
}
myLocalized {
de_DE
}
}";
public const string AllFlatFields = @"
id
version
created
createdBy
createdByUser {
id
email
displayName
}
lastModified
lastModifiedBy
lastModifiedByUser {
id
email
displayName
}
status
statusColor
url
flatData {
myJson
myJsonValue: myJson(path: ""value"")
myString
myLocalizedString
myNumber
myBoolean
myDatetime
myGeolocation
myComponent__Dynamic
myComponent {
schemaId
schemaRef1Field
}
myComponents__Dynamic
myComponents {
__typename
... on MyRefSchema1Component {
schemaId
schemaRef1Field
}
myArray {
iv {
nestedNumber
nestedNumber2
nestedBoolean
}
... on MyRefSchema2Component {
schemaId
schemaRef2Field
}
}
myTags
myArray {
nestedNumber
nestedBoolean
}
}";
public static IEnrichedContentEntity Create(NamedId<DomainId> appId, NamedId<DomainId> schemaId, DomainId id, DomainId refId, DomainId assetId, ContentData? data = null)
public static IEnrichedContentEntity Create(DomainId id, DomainId refId = default, DomainId assetId = default, ContentData? data = null)
{
var now = SystemClock.Instance.GetCurrentInstant();
data ??=
new ContentData()
.AddField("my-string",
.AddField("my-localized-string",
new ContentFieldData()
.AddLocalized("de", "value"))
.AddField("my-string2",
.AddLocalized("de-DE", "de-DE"))
.AddField("my-string",
new ContentFieldData()
.AddInvariant(null))
.AddField("my-assets",
new ContentFieldData()
.AddInvariant(JsonValue.Array(assetId.ToString())))
.AddField("2_numbers",
new ContentFieldData()
.AddInvariant(22))
.AddField("2-numbers",
new ContentFieldData()
.AddInvariant(23))
.AddField("content",
new ContentFieldData()
.AddInvariant(24))
.AddField("my-number",
new ContentFieldData()
.AddInvariant(1.0))
.AddField("my_number",
new ContentFieldData()
.AddInvariant(null))
.AddField("my-boolean",
new ContentFieldData()
.AddInvariant(true))
@ -147,48 +190,44 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new ContentFieldData()
.AddInvariant(
JsonValue.Object()
.Add(Component.Discriminator, DomainId.Empty)
.Add("value1", 100)
.Add("value2", 200)))
.Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add("schemaRef1Field", "Component1")))
.AddField("my-components",
new ContentFieldData()
.AddInvariant(
JsonValue.Array(
JsonValue.Object()
.Add(Component.Discriminator, DomainId.Empty)
.Add("value1", 100)
.Add("value2", 200))))
.Add(Component.Discriminator, TestSchemas.Ref1.Id)
.Add("schemaRef1Field", "Component1"),
JsonValue.Object()
.Add(Component.Discriminator, TestSchemas.Ref2.Id)
.Add("schemaRef2Field", "Component2"))))
.AddField("my-json",
new ContentFieldData()
.AddInvariant(
JsonValue.Object()
.Add("value", 1)))
.AddField("my-localized",
new ContentFieldData()
.AddLocalized("de-DE", "de-DE"))
.AddField("my-array",
new ContentFieldData()
.AddInvariant(JsonValue.Array(
JsonValue.Object()
.Add("nested-number", 10)
.Add("nested_number", null)
.Add("nested-boolean", true),
JsonValue.Object()
.Add("nested-number", 20)
.Add("nested_number", null)
.Add("nested-boolean", false))));
var content = new ContentEntity
{
Id = id,
AppId = appId,
AppId = TestApp.DefaultId,
Version = 1,
Created = now,
CreatedBy = RefToken.User("user1"),
LastModified = now,
LastModifiedBy = RefToken.Client("client1"),
Data = data,
SchemaId = schemaId,
SchemaId = TestSchemas.DefaultId,
Status = Status.Draft,
StatusColor = "red"
};
@ -209,6 +248,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var content = new ContentEntity
{
Id = id,
AppId = TestApp.DefaultId,
Version = 1,
Created = now,
CreatedBy = RefToken.User("user1"),
@ -248,42 +288,62 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}",
data = Data(content, false)
data = Data(content)
};
}
public static object Data(IContentEntity content, bool input, DomainId refId = default, DomainId assetId = default)
public static object FlatResponse(IEnrichedContentEntity content)
{
var result = new Dictionary<string, object>
return new
{
["gql_2Numbers"] = new
id = content.Id,
version = 1,
created = content.Created,
createdBy = content.CreatedBy.ToString(),
createdByUser = new
{
iv = 22.0
id = content.CreatedBy.Identifier,
email = $"{content.CreatedBy.Identifier}@email.com",
displayName = $"name_{content.CreatedBy.Identifier}"
},
["gql_2Numbers2"] = new
lastModified = content.LastModified,
lastModifiedBy = content.LastModifiedBy.ToString(),
lastModifiedByUser = new
{
iv = 23.0
id = content.LastModifiedBy.Identifier,
email = $"{content.LastModifiedBy}",
displayName = content.LastModifiedBy.Identifier
},
["content"] = new
status = "DRAFT",
statusColor = "red",
url = $"contents/my-schema/{content.Id}",
flatData = FlatData(content)
};
}
public static object Input(IContentEntity content, DomainId refId = default, DomainId assetId = default)
{
var result = new Dictionary<string, object>
{
["myJson"] = new
{
iv = 24.0
iv = new
{
value = 1
},
},
["myString"] = new
{
de = "value"
iv = (string?)null,
},
["myString2"] = new
["myLocalizedString"] = new
{
iv = (object?)null
de_DE = "de-DE"
},
["myNumber"] = new
{
iv = 1.0
},
["myNumber2"] = new
{
iv = (object?)null
},
["myBoolean"] = new
{
iv = true
@ -292,13 +352,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
iv = content.LastModified.ToString()
},
["myJson"] = new
{
iv = new
{
value = 1
}
},
["myGeolocation"] = new
{
iv = new
@ -307,25 +360,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
longitude = 20
}
},
["myComponent".AsDynamic(input)] = new
["myComponent"] = new
{
iv = new
iv = new Dictionary<string, object>
{
schemaId = DomainId.Empty.ToString(),
value1 = 100,
value2 = 200
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
}
},
["myComponents".AsDynamic(input)] = new
["myComponents"] = new
{
iv = new[]
{
new
new Dictionary<string, object>
{
schemaId = DomainId.Empty.ToString(),
value1 = 100,
value2 = 200
}
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
}
},
["myTags"] = new
@ -336,10 +392,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
"tag2"
}
},
["myLocalized"] = new
{
de_DE = "de-DE"
},
["myArray"] = new
{
iv = new[]
@ -347,13 +399,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
new
{
nestedNumber = 10.0,
nestedNumber2 = (object?)null,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedNumber2 = (object?)null,
nestedBoolean = false
}
}
@ -392,5 +442,204 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return result;
}
private static object Data(IContentEntity content)
{
var result = new Dictionary<string, object>
{
["myJson"] = new
{
iv = new
{
value = 1
},
ivValue = 1
},
["myString"] = new
{
iv = (string?)null,
},
["myLocalizedString"] = new
{
de_DE = "de-DE"
},
["myNumber"] = new
{
iv = 1.0
},
["myBoolean"] = new
{
iv = true
},
["myDatetime"] = new
{
iv = content.LastModified.ToString()
},
["myGeolocation"] = new
{
iv = new
{
latitude = 10,
longitude = 20
}
},
["myComponent__Dynamic"] = new
{
iv = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
}
},
["myComponent"] = new
{
iv = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
}
},
["myComponents__Dynamic"] = new
{
iv = new[]
{
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
}
},
["myComponents"] = new
{
iv = new object[]
{
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
}
},
["myTags"] = new
{
iv = new[]
{
"tag1",
"tag2"
}
},
["myArray"] = new
{
iv = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
}
};
return result;
}
private static object FlatData(IContentEntity content)
{
var result = new Dictionary<string, object?>
{
["myJson"] = new
{
value = 1
},
["myJsonValue"] = 1,
["myString"] = null,
["myLocalizedString"] = "de-DE",
["myNumber"] = 1.0,
["myBoolean"] = true,
["myDatetime"] = content.LastModified.ToString(),
["myGeolocation"] = new
{
latitude = 10,
longitude = 20
},
["myComponent__Dynamic"] = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
["myComponent"] = new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
["myComponents__Dynamic"] = new[]
{
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
},
["myComponents"] = new object[]
{
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema1Component",
["schemaId"] = TestSchemas.Ref1.Id.ToString(),
["schemaRef1Field"] = "Component1"
},
new Dictionary<string, object>
{
["__typename"] = "MyRefSchema2Component",
["schemaId"] = TestSchemas.Ref2.Id.ToString(),
["schemaRef2Field"] = "Component2"
},
},
["myTags"] = new[]
{
"tag1",
"tag2"
},
["myArray"] = new[]
{
new
{
nestedNumber = 10.0,
nestedBoolean = true
},
new
{
nestedNumber = 20.0,
nestedBoolean = false
}
}
};
return result;
}
}
}

76
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestSchemas.cs

@ -0,0 +1,76 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public static class TestSchemas
{
public static readonly NamedId<DomainId> DefaultId = NamedId.Of(DomainId.NewGuid(), "my-schema");
public static readonly NamedId<DomainId> Ref1Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema1");
public static readonly NamedId<DomainId> Ref2Id = NamedId.Of(DomainId.NewGuid(), "my-ref-schema2");
public static readonly ISchemaEntity Default;
public static readonly ISchemaEntity Ref1;
public static readonly ISchemaEntity Ref2;
static TestSchemas()
{
Ref1 = Mocks.Schema(TestApp.DefaultId, Ref1Id,
new Schema(Ref1Id.Name)
.Publish()
.AddString(1, "schemaRef1Field", Partitioning.Invariant));
Ref2 = Mocks.Schema(TestApp.DefaultId, Ref2Id,
new Schema(Ref2Id.Name)
.Publish()
.AddString(1, "schemaRef2Field", Partitioning.Invariant));
Default = Mocks.Schema(TestApp.DefaultId, DefaultId,
new Schema(DefaultId.Name)
.Publish()
.AddJson(1, "my-json", Partitioning.Invariant,
new JsonFieldProperties())
.AddString(2, "my-string", Partitioning.Invariant,
new StringFieldProperties())
.AddString(3, "my-localized-string", Partitioning.Language,
new StringFieldProperties())
.AddNumber(4, "my-number", Partitioning.Invariant,
new NumberFieldProperties())
.AddAssets(5, "my-assets", Partitioning.Invariant,
new AssetsFieldProperties())
.AddBoolean(6, "my-boolean", Partitioning.Invariant,
new BooleanFieldProperties())
.AddDateTime(7, "my-datetime", Partitioning.Invariant,
new DateTimeFieldProperties())
.AddReferences(8, "my-references", Partitioning.Invariant,
new ReferencesFieldProperties { SchemaId = Ref1Id.Id })
.AddReferences(9, "my-union", Partitioning.Invariant,
new ReferencesFieldProperties())
.AddGeolocation(10, "my-geolocation", Partitioning.Invariant,
new GeolocationFieldProperties())
.AddComponent(11, "my-component", Partitioning.Invariant,
new ComponentFieldProperties { SchemaId = Ref1Id.Id })
.AddComponents(12, "my-components", Partitioning.Invariant,
new ComponentsFieldProperties { SchemaIds = ImmutableList.Create(Ref1.Id, Ref2.Id) })
.AddTags(13, "my-tags", Partitioning.Invariant,
new TagsFieldProperties())
.AddArray(100, "my-array", Partitioning.Invariant, f => f
.AddBoolean(121, "nested-boolean",
new BooleanFieldProperties())
.AddNumber(122, "nested-number",
new NumberFieldProperties()))
.SetScripts(new SchemaScripts { Query = "<query-script>" }));
}
}
}

1884
frontend/package-lock.json

File diff suppressed because it is too large

94
frontend/package.json

@ -14,37 +14,37 @@
"build": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production",
"build:clean": "rimraf wwwroot/build",
"build:analyze": "node --max_old_space_size=4096 node_modules/webpack/bin/webpack.js --config app-config/webpack.config.js --env production --env analyze",
"postinstall": "npx patch-package && ngcc",
"ngcc": "ngcc"
"postinstall": "npx patch-package && ngcc --properties es2015 browser module main --create-ivy-entry-points",
"ngcc": "ngcc --properties es2015 browser module main --create-ivy-entry-points"
},
"dependencies": {
"@angular/animations": "12.0.0",
"@angular/cdk": "12.0.0",
"@angular/common": "12.0.0",
"@angular/core": "12.0.0",
"@angular/forms": "12.0.0",
"@angular/localize": "12.0.0",
"@angular/platform-browser": "12.0.0",
"@angular/platform-browser-dynamic": "12.0.0",
"@angular/platform-server": "12.0.0",
"@angular/router": "12.0.0",
"@angular/animations": "12.0.2",
"@angular/cdk": "12.0.2",
"@angular/common": "12.0.2",
"@angular/core": "12.0.2",
"@angular/forms": "12.0.2",
"@angular/localize": "12.0.2",
"@angular/platform-browser": "12.0.2",
"@angular/platform-browser-dynamic": "12.0.2",
"@angular/platform-server": "12.0.2",
"@angular/router": "12.0.2",
"@egjs/hammerjs": "2.0.17",
"ace-builds": "1.4.12",
"angular-gridster2": "11.2.0",
"angular-gridster2": "12.0.0",
"angular-mentions": "1.3.0",
"angular2-chartjs": "0.5.1",
"babel-polyfill": "6.26.0",
"bootstrap": "4.6.0",
"codemirror-graphql": "1.0.1",
"core-js": "3.12.1",
"codemirror-graphql": "1.0.2",
"core-js": "3.13.0",
"cropperjs": "2.0.0-alpha.1",
"date-fns": "2.21.3",
"font-awesome": "4.7.0",
"graphiql": "1.4.1",
"graphiql": "1.4.2",
"graphql": "15.5.0",
"image-focus": "1.2.0",
"keycharm": "0.4.0",
"marked": "2.0.3",
"marked": "2.0.5",
"mersenne-twister": "1.1.0",
"mousetrap": "1.6.5",
"ngx-color-picker": "11.0.0",
@ -55,10 +55,10 @@
"prop-types": "15.7.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"rxjs": "7.0.1",
"rxjs": "7.1.0",
"simplemde": "1.11.2",
"slugify": "1.5.3",
"tinymce": "5.8.0",
"tinymce": "5.8.1",
"tslib": "2.2.0",
"video.js": "7.11.8",
"vis-data": "7.1.2",
@ -67,38 +67,38 @@
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-optimizer": "0.1200.0",
"@angular/compiler": "12.0.0",
"@angular/compiler-cli": "12.0.0",
"@ngtools/webpack": "12.0.0",
"@angular-devkit/build-optimizer": "0.1200.2",
"@angular/compiler": "12.0.2",
"@angular/compiler-cli": "12.0.2",
"@ngtools/webpack": "12.0.2",
"@types/codemirror": "0.0.108",
"@types/core-js": "2.5.4",
"@types/jasmine": "3.7.4",
"@types/marked": "2.0.2",
"@types/jasmine": "3.7.5",
"@types/marked": "2.0.3",
"@types/mersenne-twister": "1.1.2",
"@types/mousetrap": "1.6.8",
"@types/node": "15.3.0",
"@types/react": "17.0.5",
"@types/node": "15.6.1",
"@types/react": "17.0.8",
"@types/react-dom": "17.0.5",
"@types/simplemde": "1.11.7",
"@types/tapable": "2.2.2",
"@types/tinymce": "4.6.1",
"@types/ws": "^7.4.4",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"@types/ws": "7.4.4",
"@typescript-eslint/eslint-plugin": "4.25.0",
"@typescript-eslint/parser": "4.25.0",
"browserslist": "4.16.6",
"caniuse-lite": "1.0.30001228",
"caniuse-lite": "1.0.30001230",
"circular-dependency-plugin": "5.2.2",
"codelyzer": "6.0.2",
"copy-webpack-plugin": "8.1.1",
"css-loader": "5.2.4",
"cssnano": "5.0.2",
"copy-webpack-plugin": "9.0.0",
"css-loader": "5.2.6",
"cssnano": "5.0.4",
"entities": "2.2.0",
"eslint": "^7.26.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-plugin-import": "^2.23.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-webpack-plugin": "^2.5.4",
"eslint": "7.27.0",
"eslint-config-airbnb-typescript": "12.3.1",
"eslint-plugin-import": "2.23.3",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-webpack-plugin": "2.5.4",
"file-loader": "6.2.0",
"html-loader": "2.1.2",
"html-webpack-plugin": "5.3.1",
@ -116,30 +116,30 @@
"karma-sourcemap-loader": "0.3.8",
"karma-webpack": "5.0.0",
"mini-css-extract-plugin": "1.6.0",
"optimize-css-assets-webpack-plugin": "5.0.4",
"optimize-css-assets-webpack-plugin": "6.0.0",
"postcss-import": "14.0.2",
"postcss-loader": "5.3.0",
"postcss-preset-env": "6.7.0",
"raw-loader": "4.0.2",
"resize-observer-polyfill": "1.5.1",
"rimraf": "3.0.2",
"sass": "^1.32.13",
"sass": "1.34.0",
"sass-lint": "1.13.1",
"sass-lint-webpack": "1.0.3",
"sass-loader": "11.1.1",
"style-loader": "2.0.0",
"sugarss": "3.0.3",
"terser": "^5.7.0",
"terser": "5.7.0",
"terser-webpack-plugin": "5.1.2",
"ts-loader": "9.1.2",
"ts-loader": "9.2.2",
"tsconfig-paths-webpack-plugin": "3.5.1",
"typemoq": "2.1.0",
"typescript": "4.2.2",
"typescript": "4.3.2",
"underscore": "1.13.1",
"webpack": "5.37.0",
"webpack-bundle-analyzer": "4.4.1",
"webpack": "5.38.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "4.7.0",
"webpack-dev-server": "3.11.2",
"webpack-filter-warnings-plugin": "^1.2.1"
"webpack-filter-warnings-plugin": "1.2.1"
}
}

Loading…
Cancel
Save