Browse Source

First simplification.

pull/630/head
Sebastian 5 years ago
parent
commit
4cf2992ed3
  1. 110
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  2. 12
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  3. 73
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs
  4. 59
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs
  5. 167
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  6. 40
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs
  7. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs
  8. 63
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs
  9. 53
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs
  10. 12
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs
  11. 19
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs
  12. 39
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs
  13. 36
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs
  14. 29
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs
  15. 39
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs
  16. 37
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs
  17. 66
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs
  18. 59
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldInputVisitor.cs
  19. 139
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldVisitor.cs
  20. 150
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs
  21. 48
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs
  22. 45
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs
  23. 8
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/Converters.cs
  24. 35
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs
  25. 83
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SchemaInfos.cs
  26. 1
      backend/src/Squidex.Web/ApiModelValidationAttribute.cs
  27. 6
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs
  28. 16
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs

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

@ -5,141 +5,163 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using GraphQL; using GraphQL;
using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Log; using Squidex.Log;
using GraphQLSchema = GraphQL.Types.Schema; using GraphQLSchema = GraphQL.Types.Schema;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
public sealed class GraphQLModel : IGraphModel public sealed class GraphQLModel
{ {
private static readonly IDocumentExecuter Executor = new DocumentExecuter(); private static readonly IDocumentExecuter Executor = new DocumentExecuter();
private readonly Dictionary<DomainId, ContentGraphType> contentTypes = new Dictionary<DomainId, ContentGraphType>(); private readonly Dictionary<SchemaInfo, ContentGraphType> contentTypes = new Dictionary<SchemaInfo, ContentGraphType>(ReferenceEqualityComparer.Instance);
private readonly GraphQLSchema graphQLSchema; private readonly Dictionary<SchemaInfo, ContentResultGraphType> contentResultTypes = new Dictionary<SchemaInfo, ContentResultGraphType>(ReferenceEqualityComparer.Instance);
private readonly GraphQLTypeFactory graphQLTypeFactory; private readonly GraphQLSchema schema;
private readonly GraphQLTypeFactory typeFactory;
private readonly ISemanticLog log; private readonly ISemanticLog log;
#pragma warning disable IDE0044 // Add readonly modifier #pragma warning disable IDE0044 // Add readonly modifier
private GraphQLTypeVisitor typeVisitor; private GraphQLFieldVisitor fieldVisitor;
#pragma warning disable IDE0044 // Add readonly modifier private GraphQLFieldInputVisitor fieldInputVisitor;
private PartitionResolver partitionResolver; private PartitionResolver partitionResolver;
#pragma warning restore IDE0044 // Add readonly modifier #pragma warning restore IDE0044 // Add readonly modifier
static GraphQLModel() static GraphQLModel()
{ {
ValueConverter.Register<JsonBoolean, bool>(x => x.Value);
ValueConverter.Register<JsonNumber, double>(x => x.Value);
ValueConverter.Register<JsonString, string>(x => x.Value);
ValueConverter.Register<JsonString, DateTimeOffset>(x => DateTimeOffset.Parse(x.Value, CultureInfo.InvariantCulture));
ValueConverter.Register<string, DomainId>(DomainId.Create); ValueConverter.Register<string, DomainId>(DomainId.Create);
} }
public GraphQLTypeFactory TypeFactory public GraphQLTypeFactory TypeFactory
{ {
get { return graphQLTypeFactory; } get { return typeFactory; }
} }
public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, GraphQLTypeFactory typeFactory, ISemanticLog log) public GraphQLModel(IAppEntity app, IEnumerable<ISchemaEntity> schemas, GraphQLTypeFactory typeFactory, ISemanticLog log)
{ {
graphQLTypeFactory = typeFactory; this.typeFactory = typeFactory;
this.log = log; this.log = log;
partitionResolver = app.PartitionResolver(); partitionResolver = app.PartitionResolver();
typeVisitor = new GraphQLTypeVisitor(contentTypes, this); fieldVisitor = new GraphQLFieldVisitor(this);
fieldInputVisitor = new GraphQLFieldInputVisitor(this);
var allSchemas = schemas.Where(x => x.SchemaDef.IsPublished).ToList(); var allSchemas = schemas.Where(x => x.SchemaDef.IsPublished).Select(SchemaInfo.Build).ToList();
BuildSchemas(allSchemas); BuildSchemas(allSchemas);
graphQLSchema = BuildSchema(this, allSchemas); schema = BuildSchema(allSchemas);
graphQLSchema.RegisterValueConverter(JsonConverter.Instance); schema.RegisterValueConverter(JsonConverter.Instance);
graphQLSchema.RegisterValueConverter(InstantConverter.Instance); schema.RegisterValueConverter(InstantConverter.Instance);
InitializeContentTypes(allSchemas); InitializeContentTypes(allSchemas);
partitionResolver = null!; partitionResolver = null!;
typeVisitor = null!; fieldVisitor = null!;
fieldInputVisitor = null!;
} }
private void BuildSchemas(List<ISchemaEntity> allSchemas) private void BuildSchemas(List<SchemaInfo> allSchemas)
{ {
foreach (var schema in allSchemas) foreach (var schemaInfo in allSchemas)
{ {
contentTypes[schema.Id] = new ContentGraphType(schema); var contentType = new ContentGraphType(schemaInfo);
contentTypes[schemaInfo] = contentType;
contentResultTypes[schemaInfo] = new ContentResultGraphType(contentType, schemaInfo);
} }
} }
private void InitializeContentTypes(List<ISchemaEntity> allSchemas) private void InitializeContentTypes(List<SchemaInfo> allSchemas)
{ {
var i = 0; foreach (var (schemaInfo, contentType) in contentTypes)
foreach (var contentType in contentTypes.Values)
{ {
var schema = allSchemas[i]; contentType.Initialize(this, schemaInfo, allSchemas);
contentType.Initialize(this, schema, allSchemas);
i++;
} }
foreach (var contentType in contentTypes.Values) foreach (var contentType in contentTypes.Values)
{ {
graphQLSchema.RegisterType(contentType); schema.RegisterType(contentType);
} }
graphQLSchema.Initialize(); schema.Initialize();
graphQLSchema.CleanupMetadata(); schema.CleanupMetadata();
} }
private static GraphQLSchema BuildSchema(GraphQLModel model, List<ISchemaEntity> schemas) private GraphQLSchema BuildSchema(List<SchemaInfo> schemas)
{ {
var schema = new GraphQLSchema var newSchema = new GraphQLSchema
{ {
Query = new AppQueriesGraphType(model, schemas) Query = new AppQueriesGraphType(this, schemas)
}; };
schema.RegisterType(ContentInterfaceGraphType.Instance); newSchema.RegisterType(ContentInterfaceGraphType.Instance);
var schemasWithFields = schemas.Where(x => x.SchemaDef.Fields.Count > 0); var schemasWithFields = schemas.Where(x => x.Fields.Count > 0);
if (schemasWithFields.Any()) if (schemasWithFields.Any())
{ {
schema.Mutation = new AppMutationsGraphType(model, schemasWithFields); newSchema.Mutation = new AppMutationsGraphType(this, schemasWithFields);
} }
return schema; return newSchema;
} }
public IFieldPartitioning ResolvePartition(Partitioning key) internal IFieldPartitioning ResolvePartition(Partitioning key)
{ {
return partitionResolver(key); return partitionResolver(key);
} }
public IGraphType? GetInputGraphType(ISchemaEntity schema, IField field, string fieldName) internal IGraphType? GetInputGraphType(FieldInfo fieldInfo)
{ {
return InputFieldVisitor.Build(field, this, schema, fieldName); return fieldInfo.Field.Accept(fieldInputVisitor, fieldInfo);
} }
public (IGraphType?, ValueResolver?, QueryArguments?) GetGraphType(ISchemaEntity schema, IField field, string fieldName) internal (IGraphType?, IFieldResolver?, QueryArguments?) GetGraphType(FieldInfo fieldInfo)
{ {
return field.Accept(typeVisitor, new GraphQLTypeVisitor.Args(schema, fieldName)); return fieldInfo.Field.Accept(fieldVisitor, fieldInfo);
} }
public IGraphType GetContentType(DomainId schemaId) internal IObjectGraphType? GetContentType(DomainId schemaId)
{
return contentTypes.FirstOrDefault(x => x.Key.Schema.Id == schemaId).Value;
}
internal IObjectGraphType GetContentType(SchemaInfo schemaId)
{ {
return contentTypes.GetOrDefault(schemaId); return contentTypes.GetOrDefault(schemaId);
} }
internal IObjectGraphType GetContentResultType(SchemaInfo schemaId)
{
return contentResultTypes.GetOrDefault(schemaId);
}
internal IEnumerable<KeyValuePair<SchemaInfo, ContentGraphType>> GetAllContentTypes()
{
return contentTypes;
}
public async Task<(object Data, object[]? Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) public async Task<(object Data, object[]? Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query)
{ {
Guard.NotNull(context, nameof(context)); Guard.NotNull(context, nameof(context));
@ -148,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
context.Setup(execution); context.Setup(execution);
execution.Schema = graphQLSchema; execution.Schema = schema;
execution.Inputs = query.Inputs; execution.Inputs = query.Inputs;
execution.Query = query.Query; execution.Query = query.Query;
}); });

12
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs

@ -60,18 +60,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public static readonly IGraphType NonNullAssetType = new NonNullGraphType(AssetType); public static readonly IGraphType NonNullAssetType = new NonNullGraphType(AssetType);
public static readonly IGraphType NoopDate = new NoopGraphType(Date);
public static readonly IGraphType NoopJson = new NoopGraphType(Json); public static readonly IGraphType NoopJson = new NoopGraphType(Json);
public static readonly IGraphType NoopFloat = new NoopGraphType(Float);
public static readonly IGraphType NoopString = new NoopGraphType(String);
public static readonly IGraphType NoopBoolean = new NoopGraphType(Boolean);
public static readonly IGraphType NoopTags = new NoopGraphType("TagsScalar");
public static readonly IGraphType NoopGeolocation = new NoopGraphType("GeolocationScalar");
} }
} }

73
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils;
@ -15,83 +16,77 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class AppMutationsGraphType : ObjectGraphType public sealed class AppMutationsGraphType : ObjectGraphType
{ {
public AppMutationsGraphType(IGraphModel model, IEnumerable<ISchemaEntity> schemas) public AppMutationsGraphType(GraphQLModel model, IEnumerable<SchemaInfo> schemas)
{ {
foreach (var schema in schemas) foreach (var schemaInfo in schemas.Where(x => x.Fields.Count > 0))
{ {
var appId = schema.AppId; var contentType = model.GetContentType(schemaInfo);
var schemaId = schema.NamedId(); var inputType = new DataInputGraphType(model, schemaInfo);
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
var contentType = model.GetContentType(schema.Id);
var inputType = new ContentDataInputGraphType(schema, schemaName, schemaType, model);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"create{schemaType}Content", Name = $"create{schemaInfo.TypeName}Content",
Arguments = ContentActions.Create.Arguments(inputType), Arguments = ContentActions.Create.Arguments(inputType),
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.Create.Resolver(appId, schemaId), Resolver = ContentActions.Create.Resolver,
Description = $"Creates an {schemaName} content." Description = $"Creates an {schemaInfo.DisplayName} content."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"update{schemaType}Content", Name = $"update{schemaInfo.TypeName}Content",
Arguments = ContentActions.Update.Arguments(inputType), Arguments = ContentActions.Update.Arguments(inputType),
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.Update.Resolver(appId, schemaId), Resolver = ContentActions.Update.Resolver,
Description = $"Update an {schemaName} content by id." Description = $"Update an {schemaInfo.DisplayName} content by id."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"upsert{schemaType}Content", Name = $"upsert{schemaInfo.TypeName}Content",
Arguments = ContentActions.Upsert.Arguments(inputType), Arguments = ContentActions.Upsert.Arguments(inputType),
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.Upsert.Resolver(appId, schemaId), Resolver = ContentActions.Upsert.Resolver,
Description = $"Upsert an {schemaName} content by id." Description = $"Upsert an {schemaInfo.DisplayName} content by id."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"patch{schemaType}Content", Name = $"patch{schemaInfo.TypeName}Content",
Arguments = ContentActions.Patch.Arguments(inputType), Arguments = ContentActions.Patch.Arguments(inputType),
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.Patch.Resolver(appId, schemaId), Resolver = ContentActions.Patch.Resolver,
Description = $"Patch an {schemaName} content by id." Description = $"Patch an {schemaInfo.DisplayName} content by id."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"change{schemaType}Content", Name = $"change{schemaInfo.TypeName}Content",
Arguments = ContentActions.ChangeStatus.Arguments, Arguments = ContentActions.ChangeStatus.Arguments,
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.ChangeStatus.Resolver(appId, schemaId), Resolver = ContentActions.ChangeStatus.Resolver,
Description = $"Change a {schemaName} content." Description = $"Change a {schemaInfo.DisplayName} content."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"delete{schemaType}Content", Name = $"delete{schemaInfo.TypeName}Content",
Arguments = ContentActions.Delete.Arguments, Arguments = ContentActions.Delete.Arguments,
ResolvedType = EntitySavedGraphType.NonNull, ResolvedType = EntitySavedGraphType.NonNull,
Resolver = ContentActions.Delete.Resolver(appId, schemaId), Resolver = ContentActions.Delete.Resolver,
Description = $"Delete an {schemaName} content." Description = $"Delete an {schemaInfo.DisplayName} content."
}); }).WithSchemaNamedId(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"publish{schemaType}Content", Name = $"publish{schemaInfo.TypeName}Content",
Arguments = ContentActions.ChangeStatus.Arguments, Arguments = ContentActions.ChangeStatus.Arguments,
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.ChangeStatus.Resolver(appId, schemaId), Resolver = ContentActions.ChangeStatus.Resolver,
Description = $"Publish a {schemaName} content.", Description = $"Publish a {schemaInfo.DisplayName} content.",
DeprecationReason = $"Use 'change{schemaType}Content' instead" DeprecationReason = $"Use 'change{schemaInfo.TypeName}Content' instead"
}); }).WithSchemaNamedId(schemaInfo);
} }
Description = "The app mutations."; Description = "The app mutations.";

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

@ -8,76 +8,61 @@
using System.Collections.Generic; using System.Collections.Generic;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class AppQueriesGraphType : ObjectGraphType public sealed class AppQueriesGraphType : ObjectGraphType
{ {
public AppQueriesGraphType(IGraphModel model, IEnumerable<ISchemaEntity> schemas) public AppQueriesGraphType(GraphQLModel model, IEnumerable<SchemaInfo> schemaInfos)
{ {
AddField(model.TypeFactory.FindAsset); AddField(model.TypeFactory.FindAsset);
AddField(model.TypeFactory.QueryAssets); AddField(model.TypeFactory.QueryAssets);
AddField(model.TypeFactory.QueryAssetsWithTotal); AddField(model.TypeFactory.QueryAssetsWithTotal);
foreach (var schema in schemas) foreach (var schemaInfo in schemaInfos)
{ {
var schemaId = schema.Id; var contentType = model.GetContentType(schemaInfo);
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
var contentType = model.GetContentType(schema.Id); AddContentFind(schemaInfo, contentType);
AddContentQueries(model, schemaInfo, contentType);
AddContentFind(
schemaId,
schemaType,
schemaName,
contentType);
AddContentQueries(
schemaId,
schemaType,
schemaName,
contentType);
} }
Description = "The app queries."; Description = "The app queries.";
} }
private void AddContentFind(DomainId schemaId, string schemaType, string schemaName, IGraphType contentType) private void AddContentFind(SchemaInfo schemaInfo, IGraphType contentType)
{ {
AddField(new FieldType AddField(new FieldType
{ {
Name = $"find{schemaType}Content", Name = $"find{schemaInfo.TypeName}Content",
Arguments = ContentActions.Find.Arguments, Arguments = ContentActions.Find.Arguments,
ResolvedType = contentType, ResolvedType = contentType,
Resolver = ContentActions.Find.Resolver(schemaId), Resolver = ContentActions.Find.Resolver,
Description = $"Find an {schemaName} content by id." Description = $"Find an {schemaInfo.DisplayName} content by id."
}); }).WithSchemaId(schemaInfo);
} }
private void AddContentQueries(DomainId schemaId, string schemaType, string schemaName, IGraphType contentType) private void AddContentQueries(GraphQLModel model, SchemaInfo schemaInfo, IGraphType contentType)
{ {
var resolver = ContentActions.QueryOrReferencing.Query(schemaId);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"query{schemaType}Contents", Name = $"query{schemaInfo.TypeName}Contents",
Arguments = ContentActions.QueryOrReferencing.Arguments, Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = resolver, Resolver = ContentActions.QueryOrReferencing.Query,
Description = $"Query {schemaName} content items." Description = $"Query {schemaInfo.DisplayName} content items."
}); }).WithSchemaId(schemaInfo);
var resultType = model.GetContentResultType(schemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"query{schemaType}ContentsWithTotal", Name = $"query{schemaInfo.TypeName}ContentsWithTotal",
Arguments = ContentActions.QueryOrReferencing.Arguments, Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = new ContentsResultGraphType(schemaType, schemaName, contentType), ResolvedType = resultType,
Resolver = resolver, Resolver = ContentActions.QueryOrReferencing.Query,
Description = $"Query {schemaName} content items with total count." Description = $"Query {schemaInfo.DisplayName} content items with total count."
}); }).WithSchemaId(schemaInfo);
} }
} }
} }

167
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using GraphQL; using GraphQL;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using GraphQL.Types; using GraphQL.Types;
@ -16,13 +15,12 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Validation; using Squidex.Infrastructure.Validation;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public static class ContentActions internal static class ContentActions
{ {
private static readonly QueryArgument Id = new QueryArgument(AllTypes.None) private static readonly QueryArgument Id = new QueryArgument(AllTypes.None)
{ {
@ -107,26 +105,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
} }
}; };
public static IFieldResolver Resolver(DomainId schemaId) public static readonly IFieldResolver Resolver = Resolvers.Async<object, object?>(async (_, fieldContext, context) =>
{ {
var schemaIdValue = schemaId.ToString(); var contentId = fieldContext.GetArgument<DomainId>("id");
return Resolvers.Async<object, object?>(async (_, fieldContext, context) => var version = fieldContext.GetArgument<int?>("version");
{
var contentId = fieldContext.GetArgument<DomainId>("id");
var version = fieldContext.GetArgument<int?>("version");
if (version >= 0) if (version >= 0)
{ {
return await context.FindContentAsync(schemaIdValue, contentId, version.Value); return await context.FindContentAsync(fieldContext.FieldDefinition.SchemaId(), contentId, version.Value);
} }
else else
{ {
return await context.FindContentAsync(contentId); return await context.FindContentAsync(contentId);
} }
}); });
}
} }
public static class QueryOrReferencing public static class QueryOrReferencing
@ -170,31 +163,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
} }
}; };
public static IFieldResolver Query(DomainId schemaId) public static readonly IFieldResolver Query = Resolvers.Async<object, object>(async (_, fieldContext, context) =>
{ {
var schemaIdValue = schemaId.ToString(); var query = fieldContext.BuildODataQuery();
return Resolvers.Async<object, object>(async (_, fieldContext, context) => return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), query);
{ });
var query = fieldContext.BuildODataQuery();
return await context.QueryContentsAsync(schemaIdValue, query);
});
}
public static IFieldResolver Referencing(DomainId schemaId) public static readonly IFieldResolver Referencing = Resolvers.Async<IContentEntity, object?>(async (source, fieldContext, context) =>
{ {
var schemaIdValue = schemaId.ToString(); var query = fieldContext.BuildODataQuery();
return Resolvers.Async<IContentEntity, object?>(async (source, fieldContext, context) =>
{
var query = fieldContext.BuildODataQuery();
var contentId = source.Id; return await context.QueryReferencingContentsAsync(fieldContext.FieldDefinition.SchemaId(), query, source.Id);
});
return await context.QueryReferencingContentsAsync(schemaIdValue, query, source.Id);
});
}
} }
public static class Create public static class Create
@ -214,26 +195,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}; };
} }
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<IEnrichedContentEntity>(appId, schemaId, c => var contentPublish = c.GetArgument<bool>("publish");
{ var contentData = GetContentData(c);
var contentPublish = c.GetArgument<bool>("publish"); var contentId = c.GetArgument<string?>("id");
var contentData = GetContentData(c);
var contentId = c.GetArgument<string?>("id");
var command = new CreateContent { Data = contentData, Publish = contentPublish }; var command = new CreateContent { Data = contentData, Publish = contentPublish };
if (!string.IsNullOrWhiteSpace(contentId)) if (!string.IsNullOrWhiteSpace(contentId))
{ {
var id = DomainId.Create(contentId); var id = DomainId.Create(contentId);
command.ContentId = id; command.ContentId = id;
} }
return command; return command;
}); });
}
} }
public static class Upsert public static class Upsert
@ -255,19 +233,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}; };
} }
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<IEnrichedContentEntity>(appId, schemaId, c => var contentPublish = c.GetArgument<bool>("publish");
{ var contentData = GetContentData(c);
var contentPublish = c.GetArgument<bool>("publish"); var contentId = c.GetArgument<string>("id");
var contentData = GetContentData(c);
var contentId = c.GetArgument<string>("id");
var id = DomainId.Create(contentId); var id = DomainId.Create(contentId);
return new UpsertContent { ContentId = id, Data = contentData, Publish = contentPublish }; return new UpsertContent { ContentId = id, Data = contentData, Publish = contentPublish };
}); });
}
} }
public static class Update public static class Update
@ -288,16 +263,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}; };
} }
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<IEnrichedContentEntity>(appId, schemaId, c => var contentId = c.GetArgument<DomainId>("id");
{ var contentData = GetContentData(c);
var contentId = c.GetArgument<DomainId>("id");
var contentData = GetContentData(c);
return new UpdateContent { ContentId = contentId, Data = contentData }; return new UpdateContent { ContentId = contentId, Data = contentData };
}); });
}
} }
public static class Patch public static class Patch
@ -318,16 +290,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}; };
} }
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<IEnrichedContentEntity>(appId, schemaId, c => var contentId = c.GetArgument<DomainId>("id");
{ var contentData = GetContentData(c);
var contentId = c.GetArgument<DomainId>("id");
var contentData = GetContentData(c);
return new PatchContent { ContentId = contentId, Data = contentData }; return new PatchContent { ContentId = contentId, Data = contentData };
}); });
}
} }
public static class ChangeStatus public static class ChangeStatus
@ -352,17 +321,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
ExpectedVersion ExpectedVersion
}; };
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<IEnrichedContentEntity>(appId, schemaId, c => var contentId = c.GetArgument<DomainId>("id");
{ var contentStatus = c.GetArgument<Status>("status");
var contentId = c.GetArgument<DomainId>("id"); var contentDueTime = c.GetArgument<Instant?>("dueTime");
var contentStatus = new Status(c.GetArgument<string>("status"));
var contentDueTime = c.GetArgument<Instant?>("dueTime");
return new ChangeContentStatus { ContentId = contentId, Status = contentStatus, DueTime = contentDueTime }; return new ChangeContentStatus { ContentId = contentId, Status = contentStatus, DueTime = contentDueTime };
}); });
}
} }
public static class Delete public static class Delete
@ -373,15 +339,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
ExpectedVersion ExpectedVersion
}; };
public static IFieldResolver Resolver(NamedId<DomainId> appId, NamedId<DomainId> schemaId) public static readonly IFieldResolver Resolver = ResolveAsync(c =>
{ {
return ResolveAsync<EntitySavedResult>(appId, schemaId, c => var contentId = c.GetArgument<DomainId>("id");
{
var contentId = c.GetArgument<DomainId>("id");
return new DeleteContent { ContentId = contentId }; return new DeleteContent { ContentId = contentId };
}); });
}
} }
private static ContentData GetContentData(IResolveFieldContext c) private static ContentData GetContentData(IResolveFieldContext c)
@ -391,21 +354,21 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return source.ToContentData((IComplexGraphType)c.FieldDefinition.Arguments.Find("data").Flatten()); return source.ToContentData((IComplexGraphType)c.FieldDefinition.Arguments.Find("data").Flatten());
} }
private static IFieldResolver ResolveAsync<T>(NamedId<DomainId> appId, NamedId<DomainId> schemaId, Func<IResolveFieldContext, ContentCommand> action) private static IFieldResolver ResolveAsync(Func<IResolveFieldContext, ContentCommand> action)
{ {
return Resolvers.Async<object, T>(async (source, fieldContext, context) => return Resolvers.Async<object, object>(async (source, fieldContext, context) =>
{ {
try try
{ {
var command = action(fieldContext); var command = action(fieldContext);
command.AppId = appId; command.AppId = fieldContext.FieldDefinition.AppId();
command.SchemaId = schemaId; command.SchemaId = fieldContext.FieldDefinition.SchemaNamedId();
command.ExpectedVersion = fieldContext.GetArgument("expectedVersion", EtagVersion.Any); command.ExpectedVersion = fieldContext.GetArgument("expectedVersion", EtagVersion.Any);
var commandContext = await context.CommandBus.PublishAsync(command); var commandContext = await context.CommandBus.PublishAsync(command);
return commandContext.Result<T>(); return commandContext.PlainResult!;
} }
catch (ValidationException ex) catch (ValidationException ex)
{ {

40
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs

@ -1,40 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
public sealed class ContentDataFlatGraphType : ObjectGraphType<FlatContentData>
{
public ContentDataFlatGraphType(ISchemaEntity schema, string schemaName, string schemaType, IGraphModel model)
{
Name = $"{schemaType}DataFlatDto";
foreach (var (field, fieldName, _) in schema.SchemaDef.Fields.SafeFields())
{
var (resolvedType, valueResolver, args) = model.GetGraphType(schema, field, fieldName);
if (valueResolver != null)
{
AddField(new FieldType
{
Name = fieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = ContentResolvers.FlatPartition(valueResolver, field.Name),
Description = field.RawProperties.Hints
});
}
}
Description = $"The structure of the flat {schemaName} data type.";
}
}
}

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

@ -12,7 +12,7 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public static class ContentFields internal static class ContentFields
{ {
public static readonly FieldType Id = new FieldType public static readonly FieldType Id = new FieldType
{ {

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

@ -9,23 +9,19 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public sealed class ContentGraphType : ObjectGraphType<IEnrichedContentEntity> internal sealed class ContentGraphType : ObjectGraphType<IEnrichedContentEntity>
{ {
private readonly DomainId schemaId; private readonly DomainId schemaId;
public ContentGraphType(ISchemaEntity schema) public ContentGraphType(SchemaInfo schemaInfo)
{ {
schemaId = schema.Id; schemaId = schemaInfo.Schema.Id;
var schemaType = schema.TypeName(); Name = schemaInfo.ContentType;
var schemaName = schema.DisplayName();
Name = schemaType.SafeTypeName();
AddField(ContentFields.Id); AddField(ContentFields.Id);
AddField(ContentFields.Version); AddField(ContentFields.Version);
@ -38,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
AddResolvedInterface(ContentInterfaceGraphType.Instance); AddResolvedInterface(ContentInterfaceGraphType.Instance);
Description = $"The structure of a {schemaName} content type."; Description = $"The structure of a {schemaInfo.DisplayName} content type.";
IsTypeOf = CheckType; IsTypeOf = CheckType;
} }
@ -48,11 +44,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return value is IContentEntity content && content.SchemaId?.Id == schemaId; return value is IContentEntity content && content.SchemaId?.Id == schemaId;
} }
public void Initialize(IGraphModel model, ISchemaEntity schema, IEnumerable<ISchemaEntity> all) public void Initialize(GraphQLModel model, SchemaInfo schemaInfo, IEnumerable<SchemaInfo> all)
{ {
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
AddField(new FieldType AddField(new FieldType
{ {
Name = "url", Name = "url",
@ -61,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Description = $"The url to the content." Description = $"The url to the content."
}); });
var contentDataType = new ContentDataGraphType(schema, schemaName, schemaType, model); var contentDataType = new DataGraphType(model, schemaInfo);
if (contentDataType.Fields.Any()) if (contentDataType.Fields.Any())
{ {
@ -74,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}); });
} }
var contentDataTypeFlat = new ContentDataFlatGraphType(schema, schemaName, schemaType, model); var contentDataTypeFlat = new DataFlatGraphType(model, schemaInfo);
if (contentDataTypeFlat.Fields.Any()) if (contentDataTypeFlat.Fields.Any())
{ {
@ -87,46 +80,42 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
}); });
} }
foreach (var other in all.Where(x => References(x, schema))) foreach (var other in all.Where(x => References(x, schemaInfo)))
{ {
var referencingId = other.Id; AddReferencingQueries(model, other);
var referencingType = other.TypeName();
var referencingName = other.DisplayName();
var contentType = model.GetContentType(referencingId);
AddReferencingQueries(referencingId, referencingType, referencingName, contentType);
} }
} }
private void AddReferencingQueries(DomainId referencingId, string referencingType, string referencingName, IGraphType contentType) private void AddReferencingQueries(GraphQLModel model, SchemaInfo referencingSchemaInfo)
{ {
var resolver = ContentActions.QueryOrReferencing.Referencing(referencingId); var contentType = model.GetContentType(referencingSchemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"referencing{referencingType}Contents", Name = $"referencing{referencingSchemaInfo.TypeName}Contents",
Arguments = ContentActions.QueryOrReferencing.Arguments, Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = resolver, Resolver = ContentActions.QueryOrReferencing.Referencing,
Description = $"Query {referencingName} content items." Description = $"Query {referencingSchemaInfo.DisplayName} content items."
}); }).WithSchemaId(referencingSchemaInfo);
var contentResultsTyp = model.GetContentResultType(referencingSchemaInfo);
AddField(new FieldType AddField(new FieldType
{ {
Name = $"referencing{referencingType}ContentsWithTotal", Name = $"referencing{referencingSchemaInfo.TypeName}ContentsWithTotal",
Arguments = ContentActions.QueryOrReferencing.Arguments, Arguments = ContentActions.QueryOrReferencing.Arguments,
ResolvedType = new ContentsResultGraphType(referencingType, referencingName, contentType), ResolvedType = contentResultsTyp,
Resolver = resolver, Resolver = ContentActions.QueryOrReferencing.Referencing,
Description = $"Query {referencingName} content items with total count." Description = $"Query {referencingSchemaInfo.DisplayName} content items with total count."
}); }).WithSchemaId(referencingSchemaInfo);
} }
private static bool References(ISchemaEntity other, ISchemaEntity schema) private static bool References(SchemaInfo other, SchemaInfo schema)
{ {
var id = schema.Id; var id = schema.Schema.Id;
return other.SchemaDef.Fields.Any(x => References(x, id)); return other.Schema.SchemaDef.Fields.Any(x => References(x, id));
} }
private static bool References(IField field, DomainId id) private static bool References(IField field, DomainId id)

53
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs

@ -6,67 +6,22 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using GraphQL; using GraphQL;
using GraphQL.Resolvers; using GraphQL.Resolvers;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
internal static class ContentResolvers internal static class ContentResolvers
{ {
public static IFieldResolver NestedValue(ValueResolver valueResolver, string key) public static readonly IFieldResolver Field = Resolvers.Sync<ContentData, object?>((content, fieldContext, _) =>
{ {
return Resolvers.Sync<JsonObject, object?>((source, fieldContext, context) => var fieldName = fieldContext.FieldDefinition.SourceName();
{
if (source.TryGetValue(key, out var value))
{
return valueResolver(value, fieldContext, context);
}
return null; return content?.GetOrDefault(fieldName);
}); });
}
public static IFieldResolver Partition(ValueResolver valueResolver, string key)
{
return Resolvers.Sync<ContentFieldData, object?>((source, fieldContext, context) =>
{
if (source.TryGetValue(key, out var value) && value != null)
{
return valueResolver(value, fieldContext, context);
}
return null;
});
}
public static IFieldResolver FlatPartition(ValueResolver valueResolver, string key)
{
return Resolvers.Sync<FlatContentData, object?>((source, fieldContext, context) =>
{
if (source.TryGetValue(key, out var value) && value != null)
{
return valueResolver(value, fieldContext, context);
}
return null;
});
}
public static IFieldResolver Field(RootField field)
{
var fieldName = field.Name;
return Resolvers.Sync<ContentData, IReadOnlyDictionary<string, IJsonValue>?>(source =>
{
return source?.GetOrDefault(fieldName);
});
}
public static readonly IFieldResolver Url = Resolve((content, _, context) => public static readonly IFieldResolver Url = Resolve((content, _, context) =>
{ {

12
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentsResultGraphType.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs

@ -10,18 +10,18 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public sealed class ContentsResultGraphType : ObjectGraphType<IResultList<IContentEntity>> internal sealed class ContentResultGraphType : ObjectGraphType<IResultList<IContentEntity>>
{ {
public ContentsResultGraphType(string schemaType, string schemaName, IGraphType contentType) public ContentResultGraphType(ContentGraphType contentType, SchemaInfo schemaInfo)
{ {
Name = $"{schemaType}ResultDto"; Name = schemaInfo.ResultType;
AddField(new FieldType AddField(new FieldType
{ {
Name = "total", Name = "total",
ResolvedType = AllTypes.NonNullInt, ResolvedType = AllTypes.NonNullInt,
Resolver = ContentResolvers.ListTotal, Resolver = ContentResolvers.ListTotal,
Description = $"The total number of {schemaName} items." Description = $"The total number of {schemaInfo.DisplayName} items."
}); });
AddField(new FieldType AddField(new FieldType
@ -29,10 +29,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = "items", Name = "items",
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = ContentResolvers.ListItems, Resolver = ContentResolvers.ListItems,
Description = $"The {schemaName} items." Description = $"The {schemaInfo.DisplayName} items."
}); });
Description = $"List of {schemaName} items and total count."; Description = $"List of {schemaInfo.DisplayName} items and total count.";
} }
} }
} }

19
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs

@ -8,6 +8,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
@ -16,27 +17,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
private readonly Dictionary<DomainId, IObjectGraphType> types = new Dictionary<DomainId, IObjectGraphType>(); private readonly Dictionary<DomainId, IObjectGraphType> types = new Dictionary<DomainId, IObjectGraphType>();
public ContentUnionGraphType(string fieldName, Dictionary<DomainId, ContentGraphType> schemaTypes, IEnumerable<DomainId>? schemaIds) public ContentUnionGraphType(GraphQLModel model, FieldInfo fieldInfo, ReferencesFieldProperties properties)
{ {
Name = $"{fieldName}ReferenceUnionDto"; Name = fieldInfo.UnionType;
if (schemaIds?.Any() == true) if (properties.SchemaIds?.Any() == true)
{ {
foreach (var schemaId in schemaIds) foreach (var schemaId in properties.SchemaIds)
{ {
var schemaType = schemaTypes.GetOrDefault(schemaId); var contentType = model.GetContentType(schemaId);
if (schemaType != null) if (contentType != null)
{ {
types[schemaId] = schemaType; types[schemaId] = contentType;
} }
} }
} }
else else
{ {
foreach (var (key, value) in schemaTypes) foreach (var (key, value) in model.GetAllContentTypes())
{ {
types[key] = value; types[key.Schema.Id] = value;
} }
} }

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

@ -0,0 +1,39 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
public sealed class DataFlatGraphType : ObjectGraphType<FlatContentData>
{
public DataFlatGraphType(GraphQLModel model, SchemaInfo schemaInfo)
{
Name = schemaInfo.DataFlatType;
foreach (var fieldInfo in schemaInfo.Fields)
{
var (resolvedType, resolver, args) = model.GetGraphType(fieldInfo);
if (resolver != null)
{
AddField(new FieldType
{
Name = fieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(fieldInfo);
}
}
Description = $"The structure of the flat {schemaInfo.DisplayName} data type.";
}
}
}

36
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataGraphType.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs

@ -12,26 +12,24 @@ using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public sealed class ContentDataGraphType : ObjectGraphType<ContentData> internal sealed class DataGraphType : ObjectGraphType<ContentData>
{ {
public ContentDataGraphType(ISchemaEntity schema, string schemaName, string schemaType, IGraphModel model) public DataGraphType(GraphQLModel model, SchemaInfo schemaInfo)
{ {
Name = $"{schemaType}DataDto"; Name = schemaInfo.DataType;
foreach (var (field, fieldName, typeName) in schema.SchemaDef.Fields.SafeFields()) foreach (var fieldInfo in schemaInfo.Fields)
{ {
var (resolvedType, valueResolver, args) = model.GetGraphType(schema, field, typeName); var (resolvedType, resolver, args) = model.GetGraphType(fieldInfo);
if (valueResolver != null) if (resolver != null)
{ {
var displayName = field.DisplayName();
var fieldGraphType = new ObjectGraphType var fieldGraphType = new ObjectGraphType
{ {
Name = $"{schemaType}Data{typeName}Dto" Name = fieldInfo.TypeName
}; };
var partitioning = model.ResolvePartition(field.Partitioning); var partitioning = model.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{ {
@ -40,24 +38,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = partitionKey.EscapePartition(), Name = partitionKey.EscapePartition(),
Arguments = args, Arguments = args,
ResolvedType = resolvedType, ResolvedType = resolvedType,
Resolver = ContentResolvers.Partition(valueResolver, partitionKey), Resolver = resolver,
Description = field.RawProperties.Hints Description = fieldInfo.Field.RawProperties.Hints
}); }).WithSourceName(partitionKey);
} }
fieldGraphType.Description = $"The structure of the {displayName} field of the {schemaName} content type."; fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content type.";
AddField(new FieldType AddField(new FieldType
{ {
Name = fieldName, Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType, ResolvedType = fieldGraphType,
Resolver = ContentResolvers.Field(field), Resolver = ContentResolvers.Field,
Description = $"The {displayName} field." Description = $"The {fieldInfo.DisplayName} field."
}); }).WithSourceName(fieldInfo);
} }
} }
Description = $"The structure of the {schemaName} data type."; Description = $"The structure of the {schemaInfo.DisplayName} data type.";
} }
} }
} }

29
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataInputGraphType.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs

@ -5,33 +5,30 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public sealed class ContentDataInputGraphType : InputObjectGraphType internal sealed class DataInputGraphType : InputObjectGraphType
{ {
public ContentDataInputGraphType(ISchemaEntity schema, string schemaName, string schemaType, IGraphModel model) public DataInputGraphType(GraphQLModel model, SchemaInfo schemaInfo)
{ {
Name = $"{schemaType}DataInputDto"; Name = schemaInfo.DataInputType;
foreach (var (field, fieldName, typeName) in schema.SchemaDef.Fields.SafeFields().Where(x => x.Field.IsForApi(true))) foreach (var fieldInfo in schemaInfo.Fields)
{ {
var resolvedType = model.GetInputGraphType(schema, field, typeName); var resolvedType = model.GetInputGraphType(fieldInfo);
if (resolvedType != null) if (resolvedType != null)
{ {
var displayName = field.DisplayName();
var fieldGraphType = new InputObjectGraphType var fieldGraphType = new InputObjectGraphType
{ {
Name = $"{schemaType}Data{typeName}InputDto" Name = fieldInfo.LocalizedInputType
}; };
var partitioning = model.ResolvePartition(field.Partitioning); var partitioning = model.ResolvePartition(((RootField)fieldInfo.Field).Partitioning);
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{ {
@ -40,23 +37,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
Name = partitionKey.EscapePartition(), Name = partitionKey.EscapePartition(),
ResolvedType = resolvedType, ResolvedType = resolvedType,
Resolver = null, Resolver = null,
Description = field.RawProperties.Hints Description = fieldInfo.Field.RawProperties.Hints
}).WithSourceName(partitionKey); }).WithSourceName(partitionKey);
} }
fieldGraphType.Description = $"The structure of the {displayName} field of the {schemaName} content input type."; fieldGraphType.Description = $"The structure of the {fieldInfo.DisplayName} field of the {schemaInfo.DisplayName} content input type.";
AddField(new FieldType AddField(new FieldType
{ {
Name = fieldName, Name = fieldInfo.FieldName,
ResolvedType = fieldGraphType, ResolvedType = fieldGraphType,
Resolver = null, Resolver = null,
Description = $"The {displayName} field." Description = $"The {fieldInfo.DisplayName} field."
}).WithSourceName(field.Name); }).WithSourceName(fieldInfo);
} }
} }
Description = $"The structure of the {schemaName} data input type."; Description = $"The structure of the {schemaInfo.DisplayName} data input type.";
} }
} }
} }

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

@ -0,0 +1,39 @@
// ==========================================================================
// 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
{
public sealed class NestedGraphType : ObjectGraphType<JsonObject>
{
public NestedGraphType(GraphQLModel model, FieldInfo fieldInfo)
{
Name = fieldInfo.NestedType;
foreach (var nestedFieldInfo in fieldInfo.Fields)
{
var (resolvedType, resolver, args) = model.GetGraphType(nestedFieldInfo);
if (resolvedType != null && resolver != null)
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = resolver,
Description = $"The {fieldInfo.DisplayName}/{nestedFieldInfo.DisplayName} nested field."
}).WithSourceName(nestedFieldInfo);
}
}
Description = $"The structure of the {fieldInfo.DisplayName} nested schema.";
}
}
}

37
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs

@ -0,0 +1,37 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{
internal sealed class NestedInputGraphType : InputObjectGraphType
{
public NestedInputGraphType(GraphQLModel model, FieldInfo fieldInfo)
{
Name = fieldInfo.NestedInputType;
foreach (var nestedFieldInfo in fieldInfo.Fields)
{
var resolvedType = model.GetInputGraphType(nestedFieldInfo);
if (resolvedType != null)
{
AddField(new FieldType
{
Name = nestedFieldInfo.FieldName,
ResolvedType = resolvedType,
Resolver = null,
Description = $"The {fieldInfo.DisplayName}/{nestedFieldInfo.DisplayName} nested field."
}).WithSourceName(nestedFieldInfo);
}
}
Description = $"The structure of the {fieldInfo.DisplayName} nested schema.";
}
}
}

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

@ -12,6 +12,7 @@ using GraphQL;
using GraphQL.Types; using GraphQL.Types;
using GraphQL.Utilities; using GraphQL.Utilities;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool; using Squidex.Infrastructure.ObjectPool;
using Squidex.Text; using Squidex.Text;
@ -97,24 +98,71 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
} }
} }
public static FieldType WithSourceName(this FieldType field, object value) public static FieldType WithSourceName(this FieldType field, string value)
{
return field.WithMetadata(nameof(SourceName), value);
}
public static FieldType WithSourceName(this FieldType field, FieldInfo value)
{
return field.WithMetadata(nameof(SourceName), value.Field.Name);
}
public static string SourceName(this FieldType field)
{
return field.GetMetadata<string>(nameof(SourceName));
}
public static FieldType WithSchemaId(this FieldType field, SchemaInfo value)
{
return field.WithMetadata(nameof(SchemaId), value.Schema.Id.ToString());
}
public static string SchemaId(this FieldType field)
{
return field.GetMetadata<string>(nameof(SchemaId));
}
public static FieldType WithSchemaNamedId(this FieldType field, SchemaInfo value)
{
return field.WithMetadata(nameof(SchemaNamedId), value.Schema.NamedId());
}
public static NamedId<DomainId> SchemaNamedId(this FieldType field)
{
return field.GetMetadata<NamedId<DomainId>>(nameof(SchemaNamedId));
}
public static FieldType WithAppId(this FieldType field, NamedId<DomainId> value)
{
return field.WithMetadata(nameof(AppId), value);
}
public static NamedId<DomainId> AppId(this FieldType field)
{
return field.GetMetadata<NamedId<DomainId>>(nameof(AppId));
}
private static FieldType WithMetadata(this FieldType field, string key, object value)
{ {
if (field is MetadataProvider metadataProvider) if (field is MetadataProvider metadataProvider)
{ {
metadataProvider.Metadata = new Dictionary<string, object> if (metadataProvider.Metadata is Dictionary<string, object> dict)
{
dict[key] = value;
}
else
{ {
["sourceName"] = value metadataProvider.Metadata = new Dictionary<string, object>
}; {
[key] = value
};
}
} }
return field; return field;
} }
public static string GetSourceName(this FieldType field)
{
return field.GetMetadata("sourceName", string.Empty);
}
public static IGraphType Flatten(this QueryArgument type) public static IGraphType Flatten(this QueryArgument type)
{ {
return type.ResolvedType.Flatten(); return type.ResolvedType.Flatten();

59
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs → backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldInputVisitor.cs

@ -7,98 +7,75 @@
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{ {
public sealed class InputFieldVisitor : IFieldVisitor<IGraphType?, InputFieldVisitor.Args> public sealed class GraphQLFieldInputVisitor : IFieldVisitor<IGraphType?, FieldInfo>
{ {
private static readonly InputFieldVisitor Instance = new InputFieldVisitor(); private readonly GraphQLModel model;
public readonly struct Args public GraphQLFieldInputVisitor(GraphQLModel model)
{
public readonly IGraphModel Model;
public readonly ISchemaEntity Schema;
public readonly string SchemaField;
public Args(IGraphModel model, ISchemaEntity schema, string fieldName)
{
Model = model;
Schema = schema;
SchemaField = fieldName;
}
}
private InputFieldVisitor()
{ {
this.model = model;
} }
public static IGraphType? Build(IField field, IGraphModel model, ISchemaEntity schema, string fieldName) public IGraphType? Visit(IArrayField field, FieldInfo args)
{
var args = new Args(model, schema, fieldName);
return field.Accept(Instance, args);
}
public IGraphType? Visit(IArrayField field, Args args)
{ {
var schemaFieldType = var schemaFieldType =
new ListGraphType( new ListGraphType(
new NonNullGraphType( new NonNullGraphType(
new NestedInputGraphType(args.Model, args.Schema, field, args.SchemaField))); new NestedInputGraphType(model, args)));
return schemaFieldType; return schemaFieldType;
} }
public IGraphType? Visit(IField<AssetsFieldProperties> field, Args args) public IGraphType? Visit(IField<AssetsFieldProperties> field, FieldInfo args)
{ {
return AllTypes.References; return AllTypes.References;
} }
public IGraphType? Visit(IField<BooleanFieldProperties> field, Args args) public IGraphType? Visit(IField<BooleanFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Boolean; return AllTypes.Boolean;
} }
public IGraphType? Visit(IField<DateTimeFieldProperties> field, Args args) public IGraphType? Visit(IField<DateTimeFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Date; return AllTypes.Date;
} }
public IGraphType? Visit(IField<GeolocationFieldProperties> field, Args args) public IGraphType? Visit(IField<GeolocationFieldProperties> field, FieldInfo args)
{ {
return GeolocationInputGraphType.Nullable; return AllTypes.Json;
} }
public IGraphType? Visit(IField<JsonFieldProperties> field, Args args) public IGraphType? Visit(IField<JsonFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Json; return AllTypes.Json;
} }
public IGraphType? Visit(IField<NumberFieldProperties> field, Args args) public IGraphType? Visit(IField<NumberFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Float; return AllTypes.Float;
} }
public IGraphType? Visit(IField<ReferencesFieldProperties> field, Args args) public IGraphType? Visit(IField<ReferencesFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Json; return AllTypes.Json;
} }
public IGraphType? Visit(IField<StringFieldProperties> field, Args args) public IGraphType? Visit(IField<StringFieldProperties> field, FieldInfo args)
{ {
return AllTypes.String; return AllTypes.String;
} }
public IGraphType? Visit(IField<TagsFieldProperties> field, Args args) public IGraphType? Visit(IField<TagsFieldProperties> field, FieldInfo args)
{ {
return AllTypes.Tags; return AllTypes.Tags;
} }
public IGraphType? Visit(IField<UIFieldProperties> field, Args args) public IGraphType? Visit(IField<UIFieldProperties> field, FieldInfo args)
{ {
return null; return null;
} }

139
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldVisitor.cs

@ -0,0 +1,139 @@
// ==========================================================================
// 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;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public delegate object ValueResolver(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
internal sealed class GraphQLFieldVisitor : IFieldVisitor<(IGraphType?, IFieldResolver?, QueryArguments?), FieldInfo>
{
private static readonly IFieldResolver Noop = CreateValueResolver((value, fieldContext, contex) => value);
private static readonly IFieldResolver Json = CreateValueResolver(ContentActions.Json.Resolver);
private static readonly IFieldResolver Assets = CreateValueResolver((value, _, context) =>
{
return context.GetReferencedAssetsAsync(value);
});
private static readonly IFieldResolver References = CreateValueResolver((value, _, context) =>
{
return context.GetReferencedContentsAsync(value);
});
private readonly GraphQLModel model;
public GraphQLFieldVisitor(GraphQLModel model)
{
this.model = model;
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IArrayField field, FieldInfo args)
{
var schemaFieldType =
new ListGraphType(
new NonNullGraphType(
new NestedGraphType(model, args)));
return (schemaFieldType, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<AssetsFieldProperties> field, FieldInfo args)
{
return (model.TypeFactory.AssetsList, Assets, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<BooleanFieldProperties> field, FieldInfo args)
{
return (AllTypes.Boolean, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<DateTimeFieldProperties> field, FieldInfo args)
{
return (AllTypes.Date, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<JsonFieldProperties> field, FieldInfo args)
{
return (AllTypes.Json, Json, ContentActions.Json.Arguments);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<GeolocationFieldProperties> field, FieldInfo args)
{
return (AllTypes.Json, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<NumberFieldProperties> field, FieldInfo args)
{
return (AllTypes.Float, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<ReferencesFieldProperties> field, FieldInfo args)
{
return ResolveReferences(field, args);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<StringFieldProperties> field, FieldInfo args)
{
return (AllTypes.String, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<TagsFieldProperties> field, FieldInfo args)
{
return (AllTypes.Tags, Noop, null);
}
public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField<UIFieldProperties> field, FieldInfo args)
{
return (null, null, null);
}
private (IGraphType?, IFieldResolver?, QueryArguments?) ResolveReferences(IField<ReferencesFieldProperties> field, FieldInfo args)
{
IGraphType? contentType = model.GetContentType(field.Properties.SingleId());
if (contentType == null)
{
var union = new ContentUnionGraphType(model, args, field.Properties);
if (!union.PossibleTypes.Any())
{
return (null, null, null);
}
contentType = union;
}
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType));
return (schemaFieldType, References, null);
}
private static IFieldResolver CreateValueResolver(ValueResolver valueResolver)
{
return Resolvers.Sync<IReadOnlyDictionary<string, IJsonValue>, object?>((source, fieldContext, context) =>
{
var key = fieldContext.FieldDefinition.SourceName();
if (source.TryGetValue(key, out var value))
{
return valueResolver(value, fieldContext, context);
}
return null;
});
}
}
}

150
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs

@ -1,150 +0,0 @@
// ==========================================================================
// 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;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public delegate object ValueResolver(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
internal sealed class GraphQLTypeVisitor : IFieldVisitor<(IGraphType?, ValueResolver?, QueryArguments?), GraphQLTypeVisitor.Args>
{
private static readonly ValueResolver NoopResolver = (value, fieldContext, contex) => value;
private readonly Dictionary<DomainId, ContentGraphType> schemaTypes;
private readonly IGraphModel model;
public readonly struct Args
{
public readonly ISchemaEntity Schema;
public readonly string SchemaField;
public Args(ISchemaEntity schema, string fieldName)
{
Schema = schema;
SchemaField = fieldName;
}
}
public GraphQLTypeVisitor(Dictionary<DomainId, ContentGraphType> schemaTypes, IGraphModel model)
{
this.model = model;
this.schemaTypes = schemaTypes;
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IArrayField field, Args args)
{
var schemaFieldType =
new ListGraphType(
new NonNullGraphType(
new NestedGraphType(model, args.Schema, field, args.SchemaField)));
return (schemaFieldType, NoopResolver, null);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<AssetsFieldProperties> field, Args args)
{
return ResolveAssets();
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<BooleanFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopBoolean);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<DateTimeFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopDate);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<GeolocationFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopGeolocation);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<NumberFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopFloat);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<ReferencesFieldProperties> field, Args args)
{
return ResolveReferences(field, args);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<StringFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopString);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<TagsFieldProperties> field, Args args)
{
return ResolveDefault(AllTypes.NoopTags);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<UIFieldProperties> field, Args args)
{
return (null, null, null);
}
public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField<JsonFieldProperties> field, Args args)
{
return (AllTypes.NoopJson, ContentActions.Json.Resolver, ContentActions.Json.Arguments);
}
private static (IGraphType?, ValueResolver?, QueryArguments?) ResolveDefault(IGraphType type)
{
return (type, NoopResolver, null);
}
private (IGraphType?, ValueResolver?, QueryArguments?) ResolveAssets()
{
var resolver = new ValueResolver((value, _, context) =>
{
return context.GetReferencedAssetsAsync(value);
});
return (model.TypeFactory.AssetsList, resolver, null);
}
private (IGraphType?, ValueResolver?, QueryArguments?) ResolveReferences(IField<ReferencesFieldProperties> field, Args args)
{
IGraphType contentType = schemaTypes.GetOrDefault(field.Properties.SingleId());
if (contentType == null)
{
var union = new ContentUnionGraphType(args.SchemaField, schemaTypes, field.Properties.SchemaIds);
if (!union.PossibleTypes.Any())
{
return (null, null, null);
}
contentType = union;
}
var resolver = new ValueResolver((value, _, context) =>
{
return context.GetReferencedContentsAsync(value);
});
var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType));
return (schemaFieldType, resolver, null);
}
}
}

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

@ -1,48 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class NestedGraphType : ObjectGraphType<JsonObject>
{
public NestedGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field, string fieldName)
{
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
var fieldDisplayName = field.DisplayName();
Name = $"{schemaType}{fieldName}ChildDto";
foreach (var (nestedField, nestedName, typeName) in field.Fields.SafeFields().Where(x => x.Field.IsForApi()))
{
var (resolvedType, valueResolver, args) = model.GetGraphType(schema, nestedField, typeName);
if (resolvedType != null && valueResolver != null)
{
AddField(new FieldType
{
Name = nestedName,
Arguments = args,
ResolvedType = resolvedType,
Resolver = ContentResolvers.NestedValue(valueResolver, nestedField.Name),
Description = $"The {fieldDisplayName}/{nestedField.DisplayName()} nested field."
});
}
}
Description = $"The structure of the {schemaName}.{fieldDisplayName} nested schema.";
}
}
}

45
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs

@ -1,45 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class NestedInputGraphType : InputObjectGraphType
{
public NestedInputGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field, string fieldName)
{
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
var fieldDisplayName = field.DisplayName();
Name = $"{schemaType}{fieldName}InputChildDto";
foreach (var (nestedField, nestedName, typeName) in field.Fields.SafeFields().Where(x => x.Field.IsForApi(true)))
{
var resolvedType = model.GetInputGraphType(schema, nestedField, typeName);
if (resolvedType != null)
{
AddField(new FieldType
{
Name = nestedName,
ResolvedType = resolvedType,
Resolver = null,
Description = $"The {fieldDisplayName}/{nestedField.DisplayName()} nested field."
}).WithSourceName(nestedField.Name);
}
}
Description = $"The structure of the {schemaName}.{fieldDisplayName} nested schema.";
}
}
}

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

@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
{ {
if (source.TryGetValue(field.Name, out var t) && t is IDictionary<string, object> nested && field.ResolvedType is IComplexGraphType complexType) if (source.TryGetValue(field.Name, out var t) && t is IDictionary<string, object> nested && field.ResolvedType is IComplexGraphType complexType)
{ {
result[field.GetSourceName()] = nested.ToFieldData(complexType); result[field.SourceName()] = nested.ToFieldData(complexType);
} }
} }
@ -49,11 +49,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
} }
} }
result[field.GetSourceName()] = arr; result[field.SourceName()] = arr;
} }
else else
{ {
result[field.GetSourceName()] = JsonConverter.ParseJson(value); result[field.SourceName()] = JsonConverter.ParseJson(value);
} }
} }
} }
@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
{ {
if (source.TryGetValue(field.Name, out var value)) if (source.TryGetValue(field.Name, out var value))
{ {
result[field.GetSourceName()] = JsonConverter.ParseJson(value); result[field.SourceName()] = JsonConverter.ParseJson(value);
} }
} }

35
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs

@ -1,35 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
internal sealed class GeolocationInputGraphType : InputObjectGraphType
{
public static readonly IGraphType Nullable = new GeolocationInputGraphType();
public static readonly IGraphType NonNull = new NonNullGraphType(Nullable);
private GeolocationInputGraphType()
{
Name = "GeolocationInputDto";
AddField(new FieldType
{
Name = "latitude",
ResolvedType = AllTypes.NonNullFloat
});
AddField(new FieldType
{
Name = "longitude",
ResolvedType = AllTypes.NonNullFloat
});
}
}
}

83
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SchemaInfos.cs

@ -0,0 +1,83 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed record SchemaInfo(ISchemaEntity Schema, string TypeName, IReadOnlyList<FieldInfo> Fields)
{
public string DisplayName { get; set; } = Schema.DisplayName();
public string ContentType { get; } = TypeName.SafeTypeName();
public string DataType { get; } = $"{TypeName}DataDto";
public string DataInputType { get; } = $"{TypeName}DataInputDto";
public string DataFlatType { get; } = $"{TypeName}FlatDataDto";
public string ResultType { get; } = $"{TypeName}ResultDto";
public static SchemaInfo Build(ISchemaEntity schema)
{
var typeName = schema.TypeName();
var fields =
schema.SchemaDef.Fields.SafeFields()
.Select(x => FieldInfo.Build(x.Field, x.Name, $"{typeName}{x.Type}"))
.ToList();
return new SchemaInfo(
schema,
schema.TypeName(),
fields);
}
}
public sealed record FieldInfo(IField Field, string FieldName, string TypeName, IReadOnlyList<FieldInfo> Fields)
{
private static readonly IReadOnlyList<FieldInfo> EmptyFields = new List<FieldInfo>();
public string DisplayName { get; set; } = Field.DisplayName();
public string LocalizedType { get; } = $"{TypeName}Dto";
public string LocalizedInputType { get; } = $"{TypeName}InputDto";
public string NestedType { get; } = $"{TypeName}ChildDto";
public string NestedInputType { get; } = $"{TypeName}ChildInputDto";
public string UnionType { get; } = $"{TypeName}UnionDto";
public static FieldInfo Build(IRootField rootField, string fieldName, string typeName)
{
var fields = EmptyFields;
if (rootField is IArrayField arrayField)
{
fields =
arrayField.Fields.SafeFields()
.Select(x => Build(x.Field, x.Name, $"{typeName}{x.Type}"))
.ToList();
}
return new FieldInfo(rootField, fieldName, typeName, fields);
}
public static FieldInfo Build(INestedField nestedField, string fieldName, string fieldTypeName)
{
return new FieldInfo(nestedField, fieldName, fieldTypeName, EmptyFields);
}
}
}

1
backend/src/Squidex.Web/ApiModelValidationAttribute.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;

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

@ -221,7 +221,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
flatData = new flatData = new
{ {
myString = "value", myString = "value",
myNumber = 1, myNumber = 1.0,
myBoolean = true, myBoolean = true,
myDatetime = content.LastModified, myDatetime = content.LastModified,
myJsonValue = 1, myJsonValue = 1,
@ -244,12 +244,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
new new
{ {
nestedNumber = 10, nestedNumber = 10.0,
nestedBoolean = true nestedBoolean = true
}, },
new new
{ {
nestedNumber = 20, nestedNumber = 20.0,
nestedBoolean = false nestedBoolean = false
} }
} }

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

@ -196,11 +196,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
["gql_2Numbers"] = new ["gql_2Numbers"] = new
{ {
iv = 22 iv = 22.0
}, },
["gql_2Numbers2"] = new ["gql_2Numbers2"] = new
{ {
iv = 23 iv = 23.0
}, },
["myString"] = new ["myString"] = new
{ {
@ -208,11 +208,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}, },
["myNumber"] = new ["myNumber"] = new
{ {
iv = 1 iv = 1.0
}, },
["myNumber2"] = new ["myNumber2"] = new
{ {
iv = 2 iv = 2.0
}, },
["myBoolean"] = new ["myBoolean"] = new
{ {
@ -255,14 +255,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
new new
{ {
nestedNumber = 10, nestedNumber = 10.0,
nestedNumber2 = 11, nestedNumber2 = 11.0,
nestedBoolean = true nestedBoolean = true
}, },
new new
{ {
nestedNumber = 20, nestedNumber = 20.0,
nestedNumber2 = 21, nestedNumber2 = 21.0,
nestedBoolean = false nestedBoolean = false
} }
} }

Loading…
Cancel
Save