From 4cf2992ed3e0f53780cc74df37c58c1163706791 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 29 Jan 2021 20:28:28 +0100 Subject: [PATCH] First simplification. --- .../Contents/GraphQL/GraphQLModel.cs | 110 +++++++----- .../Contents/GraphQL/Types/AllTypes.cs | 12 -- .../GraphQL/Types/AppMutationsGraphType.cs | 73 ++++---- .../GraphQL/Types/AppQueriesGraphType.cs | 59 +++---- .../GraphQL/Types/Contents/ContentActions.cs | 167 +++++++----------- .../Contents/ContentDataFlatGraphType.cs | 40 ----- .../GraphQL/Types/Contents/ContentFields.cs | 2 +- .../Types/Contents/ContentGraphType.cs | 63 +++---- .../Types/Contents/ContentResolvers.cs | 53 +----- ...GraphType.cs => ContentResultGraphType.cs} | 12 +- .../Types/Contents/ContentUnionGraphType.cs | 19 +- .../Types/Contents/DataFlatGraphType.cs | 39 ++++ ...ntentDataGraphType.cs => DataGraphType.cs} | 36 ++-- ...nputGraphType.cs => DataInputGraphType.cs} | 29 ++- .../GraphQL/Types/Contents/NestedGraphType.cs | 39 ++++ .../Types/Contents/NestedInputGraphType.cs | 37 ++++ .../Contents/GraphQL/Types/Extensions.cs | 66 ++++++- ...Visitor.cs => GraphQLFieldInputVisitor.cs} | 59 ++----- .../GraphQL/Types/GraphQLFieldVisitor.cs | 139 +++++++++++++++ .../GraphQL/Types/GraphQLTypeVisitor.cs | 150 ---------------- .../Contents/GraphQL/Types/NestedGraphType.cs | 48 ----- .../GraphQL/Types/NestedInputGraphType.cs | 45 ----- .../GraphQL/Types/Primitives/Converters.cs | 8 +- .../Primitives/GeolocationInputGraphType.cs | 35 ---- .../Contents/GraphQL/Types/SchemaInfos.cs | 83 +++++++++ .../ApiModelValidationAttribute.cs | 1 - .../Contents/GraphQL/GraphQLQueriesTests.cs | 6 +- .../Contents/GraphQL/TestContent.cs | 16 +- 28 files changed, 691 insertions(+), 755 deletions(-) delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs rename backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/{ContentsResultGraphType.cs => ContentResultGraphType.cs} (67%) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs rename backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/{ContentDataGraphType.cs => DataGraphType.cs} (52%) rename backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/{ContentDataInputGraphType.cs => DataInputGraphType.cs} (57%) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs rename backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/{InputFieldVisitor.cs => GraphQLFieldInputVisitor.cs} (57%) create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldVisitor.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs delete mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs create mode 100644 backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SchemaInfos.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index e28fc4a5f..177cfcb0d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -5,141 +5,163 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using GraphQL; +using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; 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.Primitives; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Squidex.Log; using GraphQLSchema = GraphQL.Types.Schema; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public sealed class GraphQLModel : IGraphModel + public sealed class GraphQLModel { private static readonly IDocumentExecuter Executor = new DocumentExecuter(); - private readonly Dictionary contentTypes = new Dictionary(); - private readonly GraphQLSchema graphQLSchema; - private readonly GraphQLTypeFactory graphQLTypeFactory; + private readonly Dictionary contentTypes = new Dictionary(ReferenceEqualityComparer.Instance); + private readonly Dictionary contentResultTypes = new Dictionary(ReferenceEqualityComparer.Instance); + private readonly GraphQLSchema schema; + private readonly GraphQLTypeFactory typeFactory; private readonly ISemanticLog log; #pragma warning disable IDE0044 // Add readonly modifier - private GraphQLTypeVisitor typeVisitor; -#pragma warning disable IDE0044 // Add readonly modifier + private GraphQLFieldVisitor fieldVisitor; + private GraphQLFieldInputVisitor fieldInputVisitor; private PartitionResolver partitionResolver; #pragma warning restore IDE0044 // Add readonly modifier static GraphQLModel() { + ValueConverter.Register(x => x.Value); + ValueConverter.Register(x => x.Value); + ValueConverter.Register(x => x.Value); + ValueConverter.Register(x => DateTimeOffset.Parse(x.Value, CultureInfo.InvariantCulture)); ValueConverter.Register(DomainId.Create); } public GraphQLTypeFactory TypeFactory { - get { return graphQLTypeFactory; } + get { return typeFactory; } } public GraphQLModel(IAppEntity app, IEnumerable schemas, GraphQLTypeFactory typeFactory, ISemanticLog log) { - graphQLTypeFactory = typeFactory; + this.typeFactory = typeFactory; this.log = log; 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); - graphQLSchema = BuildSchema(this, allSchemas); - graphQLSchema.RegisterValueConverter(JsonConverter.Instance); - graphQLSchema.RegisterValueConverter(InstantConverter.Instance); + schema = BuildSchema(allSchemas); + schema.RegisterValueConverter(JsonConverter.Instance); + schema.RegisterValueConverter(InstantConverter.Instance); InitializeContentTypes(allSchemas); partitionResolver = null!; - typeVisitor = null!; + fieldVisitor = null!; + fieldInputVisitor = null!; } - private void BuildSchemas(List allSchemas) + private void BuildSchemas(List 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 allSchemas) + private void InitializeContentTypes(List allSchemas) { - var i = 0; - - foreach (var contentType in contentTypes.Values) + foreach (var (schemaInfo, contentType) in contentTypes) { - var schema = allSchemas[i]; - - contentType.Initialize(this, schema, allSchemas); - - i++; + contentType.Initialize(this, schemaInfo, allSchemas); } foreach (var contentType in contentTypes.Values) { - graphQLSchema.RegisterType(contentType); + schema.RegisterType(contentType); } - graphQLSchema.Initialize(); - graphQLSchema.CleanupMetadata(); + schema.Initialize(); + schema.CleanupMetadata(); } - private static GraphQLSchema BuildSchema(GraphQLModel model, List schemas) + private GraphQLSchema BuildSchema(List 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()) { - 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); } - 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); } + internal IObjectGraphType GetContentResultType(SchemaInfo schemaId) + { + return contentResultTypes.GetOrDefault(schemaId); + } + + internal IEnumerable> GetAllContentTypes() + { + return contentTypes; + } + public async Task<(object Data, object[]? Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) { Guard.NotNull(context, nameof(context)); @@ -148,7 +170,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { context.Setup(execution); - execution.Schema = graphQLSchema; + execution.Schema = schema; execution.Inputs = query.Inputs; execution.Query = query.Query; }); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs index 8c3c8a5a1..44d99e22d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs +++ b/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 NoopDate = new NoopGraphType(Date); - 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"); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs index 5d9ec566e..17f4fc9b6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Collections.Generic; +using System.Linq; using GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; 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 AppMutationsGraphType(IGraphModel model, IEnumerable schemas) + public AppMutationsGraphType(GraphQLModel model, IEnumerable 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 schemaType = schema.TypeName(); - var schemaName = schema.DisplayName(); - - var contentType = model.GetContentType(schema.Id); - - var inputType = new ContentDataInputGraphType(schema, schemaName, schemaType, model); + var inputType = new DataInputGraphType(model, schemaInfo); AddField(new FieldType { - Name = $"create{schemaType}Content", + Name = $"create{schemaInfo.TypeName}Content", Arguments = ContentActions.Create.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.Create.Resolver(appId, schemaId), - Description = $"Creates an {schemaName} content." - }); + Resolver = ContentActions.Create.Resolver, + Description = $"Creates an {schemaInfo.DisplayName} content." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"update{schemaType}Content", + Name = $"update{schemaInfo.TypeName}Content", Arguments = ContentActions.Update.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.Update.Resolver(appId, schemaId), - Description = $"Update an {schemaName} content by id." - }); + Resolver = ContentActions.Update.Resolver, + Description = $"Update an {schemaInfo.DisplayName} content by id." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"upsert{schemaType}Content", + Name = $"upsert{schemaInfo.TypeName}Content", Arguments = ContentActions.Upsert.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.Upsert.Resolver(appId, schemaId), - Description = $"Upsert an {schemaName} content by id." - }); + Resolver = ContentActions.Upsert.Resolver, + Description = $"Upsert an {schemaInfo.DisplayName} content by id." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"patch{schemaType}Content", + Name = $"patch{schemaInfo.TypeName}Content", Arguments = ContentActions.Patch.Arguments(inputType), ResolvedType = contentType, - Resolver = ContentActions.Patch.Resolver(appId, schemaId), - Description = $"Patch an {schemaName} content by id." - }); + Resolver = ContentActions.Patch.Resolver, + Description = $"Patch an {schemaInfo.DisplayName} content by id." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"change{schemaType}Content", + Name = $"change{schemaInfo.TypeName}Content", Arguments = ContentActions.ChangeStatus.Arguments, ResolvedType = contentType, - Resolver = ContentActions.ChangeStatus.Resolver(appId, schemaId), - Description = $"Change a {schemaName} content." - }); + Resolver = ContentActions.ChangeStatus.Resolver, + Description = $"Change a {schemaInfo.DisplayName} content." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"delete{schemaType}Content", + Name = $"delete{schemaInfo.TypeName}Content", Arguments = ContentActions.Delete.Arguments, ResolvedType = EntitySavedGraphType.NonNull, - Resolver = ContentActions.Delete.Resolver(appId, schemaId), - Description = $"Delete an {schemaName} content." - }); + Resolver = ContentActions.Delete.Resolver, + Description = $"Delete an {schemaInfo.DisplayName} content." + }).WithSchemaNamedId(schemaInfo); AddField(new FieldType { - Name = $"publish{schemaType}Content", + Name = $"publish{schemaInfo.TypeName}Content", Arguments = ContentActions.ChangeStatus.Arguments, ResolvedType = contentType, - Resolver = ContentActions.ChangeStatus.Resolver(appId, schemaId), - Description = $"Publish a {schemaName} content.", - DeprecationReason = $"Use 'change{schemaType}Content' instead" - }); + Resolver = ContentActions.ChangeStatus.Resolver, + Description = $"Publish a {schemaInfo.DisplayName} content.", + DeprecationReason = $"Use 'change{schemaInfo.TypeName}Content' instead" + }).WithSchemaNamedId(schemaInfo); } Description = "The app mutations."; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs index cf8ed66d6..813f9d14f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs @@ -8,76 +8,61 @@ using System.Collections.Generic; using GraphQL.Types; 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 { public sealed class AppQueriesGraphType : ObjectGraphType { - public AppQueriesGraphType(IGraphModel model, IEnumerable schemas) + public AppQueriesGraphType(GraphQLModel model, IEnumerable schemaInfos) { AddField(model.TypeFactory.FindAsset); AddField(model.TypeFactory.QueryAssets); AddField(model.TypeFactory.QueryAssetsWithTotal); - foreach (var schema in schemas) + foreach (var schemaInfo in schemaInfos) { - var schemaId = schema.Id; - var schemaType = schema.TypeName(); - var schemaName = schema.DisplayName(); + var contentType = model.GetContentType(schemaInfo); - var contentType = model.GetContentType(schema.Id); - - AddContentFind( - schemaId, - schemaType, - schemaName, - contentType); - - AddContentQueries( - schemaId, - schemaType, - schemaName, - contentType); + AddContentFind(schemaInfo, contentType); + AddContentQueries(model, schemaInfo, contentType); } 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 { - Name = $"find{schemaType}Content", + Name = $"find{schemaInfo.TypeName}Content", Arguments = ContentActions.Find.Arguments, ResolvedType = contentType, - Resolver = ContentActions.Find.Resolver(schemaId), - Description = $"Find an {schemaName} content by id." - }); + Resolver = ContentActions.Find.Resolver, + 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 { - Name = $"query{schemaType}Contents", + Name = $"query{schemaInfo.TypeName}Contents", Arguments = ContentActions.QueryOrReferencing.Arguments, ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), - Resolver = resolver, - Description = $"Query {schemaName} content items." - }); + Resolver = ContentActions.QueryOrReferencing.Query, + Description = $"Query {schemaInfo.DisplayName} content items." + }).WithSchemaId(schemaInfo); + + var resultType = model.GetContentResultType(schemaInfo); AddField(new FieldType { - Name = $"query{schemaType}ContentsWithTotal", + Name = $"query{schemaInfo.TypeName}ContentsWithTotal", Arguments = ContentActions.QueryOrReferencing.Arguments, - ResolvedType = new ContentsResultGraphType(schemaType, schemaName, contentType), - Resolver = resolver, - Description = $"Query {schemaName} content items with total count." - }); + ResolvedType = resultType, + Resolver = ContentActions.QueryOrReferencing.Query, + Description = $"Query {schemaInfo.DisplayName} content items with total count." + }).WithSchemaId(schemaInfo); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs index 83e8ff1d6..346f8af0a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; using GraphQL; using GraphQL.Resolvers; 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.GraphQL.Types.Primitives; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Validation; 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) { @@ -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(async (_, fieldContext, context) => { - var schemaIdValue = schemaId.ToString(); + var contentId = fieldContext.GetArgument("id"); - return Resolvers.Async(async (_, fieldContext, context) => - { - var contentId = fieldContext.GetArgument("id"); - - var version = fieldContext.GetArgument("version"); + var version = fieldContext.GetArgument("version"); - if (version >= 0) - { - return await context.FindContentAsync(schemaIdValue, contentId, version.Value); - } - else - { - return await context.FindContentAsync(contentId); - } - }); - } + if (version >= 0) + { + return await context.FindContentAsync(fieldContext.FieldDefinition.SchemaId(), contentId, version.Value); + } + else + { + return await context.FindContentAsync(contentId); + } + }); } 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(async (_, fieldContext, context) => { - var schemaIdValue = schemaId.ToString(); + var query = fieldContext.BuildODataQuery(); - return Resolvers.Async(async (_, fieldContext, context) => - { - var query = fieldContext.BuildODataQuery(); - - return await context.QueryContentsAsync(schemaIdValue, query); - }); - } + return await context.QueryContentsAsync(fieldContext.FieldDefinition.SchemaId(), query); + }); - public static IFieldResolver Referencing(DomainId schemaId) + public static readonly IFieldResolver Referencing = Resolvers.Async(async (source, fieldContext, context) => { - var schemaIdValue = schemaId.ToString(); - - return Resolvers.Async(async (source, fieldContext, context) => - { - var query = fieldContext.BuildODataQuery(); + var query = fieldContext.BuildODataQuery(); - var contentId = source.Id; - - return await context.QueryReferencingContentsAsync(schemaIdValue, query, source.Id); - }); - } + return await context.QueryReferencingContentsAsync(fieldContext.FieldDefinition.SchemaId(), query, source.Id); + }); } public static class Create @@ -214,26 +195,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents }; } - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentPublish = c.GetArgument("publish"); - var contentData = GetContentData(c); - var contentId = c.GetArgument("id"); + var contentPublish = c.GetArgument("publish"); + var contentData = GetContentData(c); + var contentId = c.GetArgument("id"); - var command = new CreateContent { Data = contentData, Publish = contentPublish }; + var command = new CreateContent { Data = contentData, Publish = contentPublish }; - if (!string.IsNullOrWhiteSpace(contentId)) - { - var id = DomainId.Create(contentId); + if (!string.IsNullOrWhiteSpace(contentId)) + { + var id = DomainId.Create(contentId); - command.ContentId = id; - } + command.ContentId = id; + } - return command; - }); - } + return command; + }); } public static class Upsert @@ -255,19 +233,16 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents }; } - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentPublish = c.GetArgument("publish"); - var contentData = GetContentData(c); - var contentId = c.GetArgument("id"); + var contentPublish = c.GetArgument("publish"); + var contentData = GetContentData(c); + var contentId = c.GetArgument("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 @@ -288,16 +263,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents }; } - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentId = c.GetArgument("id"); - var contentData = GetContentData(c); + var contentId = c.GetArgument("id"); + var contentData = GetContentData(c); - return new UpdateContent { ContentId = contentId, Data = contentData }; - }); - } + return new UpdateContent { ContentId = contentId, Data = contentData }; + }); } public static class Patch @@ -318,16 +290,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents }; } - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentId = c.GetArgument("id"); - var contentData = GetContentData(c); + var contentId = c.GetArgument("id"); + var contentData = GetContentData(c); - return new PatchContent { ContentId = contentId, Data = contentData }; - }); - } + return new PatchContent { ContentId = contentId, Data = contentData }; + }); } public static class ChangeStatus @@ -352,17 +321,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents ExpectedVersion }; - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentId = c.GetArgument("id"); - var contentStatus = new Status(c.GetArgument("status")); - var contentDueTime = c.GetArgument("dueTime"); + var contentId = c.GetArgument("id"); + var contentStatus = c.GetArgument("status"); + var contentDueTime = c.GetArgument("dueTime"); - return new ChangeContentStatus { ContentId = contentId, Status = contentStatus, DueTime = contentDueTime }; - }); - } + return new ChangeContentStatus { ContentId = contentId, Status = contentStatus, DueTime = contentDueTime }; + }); } public static class Delete @@ -373,15 +339,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents ExpectedVersion }; - public static IFieldResolver Resolver(NamedId appId, NamedId schemaId) + public static readonly IFieldResolver Resolver = ResolveAsync(c => { - return ResolveAsync(appId, schemaId, c => - { - var contentId = c.GetArgument("id"); + var contentId = c.GetArgument("id"); - return new DeleteContent { ContentId = contentId }; - }); - } + return new DeleteContent { ContentId = contentId }; + }); } 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()); } - private static IFieldResolver ResolveAsync(NamedId appId, NamedId schemaId, Func action) + private static IFieldResolver ResolveAsync(Func action) { - return Resolvers.Async(async (source, fieldContext, context) => + return Resolvers.Async(async (source, fieldContext, context) => { try { var command = action(fieldContext); - command.AppId = appId; - command.SchemaId = schemaId; + command.AppId = fieldContext.FieldDefinition.AppId(); + command.SchemaId = fieldContext.FieldDefinition.SchemaNamedId(); command.ExpectedVersion = fieldContext.GetArgument("expectedVersion", EtagVersion.Any); var commandContext = await context.CommandBus.PublishAsync(command); - return commandContext.Result(); + return commandContext.PlainResult!; } catch (ValidationException ex) { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs deleted file mode 100644 index 93fb6653f..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataFlatGraphType.cs +++ /dev/null @@ -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 - { - 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."; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs index 299fb2f0a..c33c4fd96 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentFields.cs +++ b/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 { - public static class ContentFields + internal static class ContentFields { public static readonly FieldType Id = new FieldType { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs index ac009f6f9..6fac327cd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentGraphType.cs @@ -9,23 +9,19 @@ using System.Collections.Generic; using System.Linq; using GraphQL.Types; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { - public sealed class ContentGraphType : ObjectGraphType + internal sealed class ContentGraphType : ObjectGraphType { private readonly DomainId schemaId; - public ContentGraphType(ISchemaEntity schema) + public ContentGraphType(SchemaInfo schemaInfo) { - schemaId = schema.Id; + schemaId = schemaInfo.Schema.Id; - var schemaType = schema.TypeName(); - var schemaName = schema.DisplayName(); - - Name = schemaType.SafeTypeName(); + Name = schemaInfo.ContentType; AddField(ContentFields.Id); AddField(ContentFields.Version); @@ -38,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents AddResolvedInterface(ContentInterfaceGraphType.Instance); - Description = $"The structure of a {schemaName} content type."; + Description = $"The structure of a {schemaInfo.DisplayName} content type."; IsTypeOf = CheckType; } @@ -48,11 +44,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents return value is IContentEntity content && content.SchemaId?.Id == schemaId; } - public void Initialize(IGraphModel model, ISchemaEntity schema, IEnumerable all) + public void Initialize(GraphQLModel model, SchemaInfo schemaInfo, IEnumerable all) { - var schemaType = schema.TypeName(); - var schemaName = schema.DisplayName(); - AddField(new FieldType { Name = "url", @@ -61,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents Description = $"The url to the content." }); - var contentDataType = new ContentDataGraphType(schema, schemaName, schemaType, model); + var contentDataType = new DataGraphType(model, schemaInfo); 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()) { @@ -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; - var referencingType = other.TypeName(); - var referencingName = other.DisplayName(); - - var contentType = model.GetContentType(referencingId); - - AddReferencingQueries(referencingId, referencingType, referencingName, contentType); + AddReferencingQueries(model, other); } } - 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 { - Name = $"referencing{referencingType}Contents", + Name = $"referencing{referencingSchemaInfo.TypeName}Contents", Arguments = ContentActions.QueryOrReferencing.Arguments, ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), - Resolver = resolver, - Description = $"Query {referencingName} content items." - }); + Resolver = ContentActions.QueryOrReferencing.Referencing, + Description = $"Query {referencingSchemaInfo.DisplayName} content items." + }).WithSchemaId(referencingSchemaInfo); + + var contentResultsTyp = model.GetContentResultType(referencingSchemaInfo); AddField(new FieldType { - Name = $"referencing{referencingType}ContentsWithTotal", + Name = $"referencing{referencingSchemaInfo.TypeName}ContentsWithTotal", Arguments = ContentActions.QueryOrReferencing.Arguments, - ResolvedType = new ContentsResultGraphType(referencingType, referencingName, contentType), - Resolver = resolver, - Description = $"Query {referencingName} content items with total count." - }); + ResolvedType = contentResultsTyp, + Resolver = ContentActions.QueryOrReferencing.Referencing, + 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) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs index 0f740476e..2f06a63eb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResolvers.cs @@ -6,67 +6,22 @@ // ========================================================================== using System; -using System.Collections.Generic; using GraphQL; using GraphQL.Resolvers; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents { internal static class ContentResolvers { - public static IFieldResolver NestedValue(ValueResolver valueResolver, string key) + public static readonly IFieldResolver Field = Resolvers.Sync((content, fieldContext, _) => { - return Resolvers.Sync((source, fieldContext, context) => - { - if (source.TryGetValue(key, out var value)) - { - return valueResolver(value, fieldContext, context); - } + var fieldName = fieldContext.FieldDefinition.SourceName(); - return null; - }); - } - - public static IFieldResolver Partition(ValueResolver valueResolver, string key) - { - return Resolvers.Sync((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((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?>(source => - { - return source?.GetOrDefault(fieldName); - }); - } + return content?.GetOrDefault(fieldName); + }); public static readonly IFieldResolver Url = Resolve((content, _, context) => { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentsResultGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs similarity index 67% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentsResultGraphType.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentResultGraphType.cs index fe91f09e1..6dbb55433 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentsResultGraphType.cs +++ b/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 { - public sealed class ContentsResultGraphType : ObjectGraphType> + internal sealed class ContentResultGraphType : ObjectGraphType> { - public ContentsResultGraphType(string schemaType, string schemaName, IGraphType contentType) + public ContentResultGraphType(ContentGraphType contentType, SchemaInfo schemaInfo) { - Name = $"{schemaType}ResultDto"; + Name = schemaInfo.ResultType; AddField(new FieldType { Name = "total", ResolvedType = AllTypes.NonNullInt, Resolver = ContentResolvers.ListTotal, - Description = $"The total number of {schemaName} items." + Description = $"The total number of {schemaInfo.DisplayName} items." }); AddField(new FieldType @@ -29,10 +29,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents Name = "items", ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), 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."; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs index ba3aced69..03a459a91 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentUnionGraphType.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using GraphQL.Types; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; 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 types = new Dictionary(); - public ContentUnionGraphType(string fieldName, Dictionary schemaTypes, IEnumerable? 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 { - foreach (var (key, value) in schemaTypes) + foreach (var (key, value) in model.GetAllContentTypes()) { - types[key] = value; + types[key.Schema.Id] = value; } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataFlatGraphType.cs new file mode 100644 index 000000000..d646059d2 --- /dev/null +++ b/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 + { + 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."; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs similarity index 52% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataGraphType.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataGraphType.cs index d407801dd..42042efc1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataGraphType.cs +++ b/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 { - public sealed class ContentDataGraphType : ObjectGraphType + internal sealed class DataGraphType : ObjectGraphType { - 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 { - 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) { @@ -40,24 +38,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents Name = partitionKey.EscapePartition(), Arguments = args, ResolvedType = resolvedType, - Resolver = ContentResolvers.Partition(valueResolver, partitionKey), - Description = field.RawProperties.Hints - }); + Resolver = resolver, + 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 { - Name = fieldName, + Name = fieldInfo.FieldName, ResolvedType = fieldGraphType, - Resolver = ContentResolvers.Field(field), - Description = $"The {displayName} field." - }); + Resolver = ContentResolvers.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."; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs similarity index 57% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataInputGraphType.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs index ec3b5fec3..95811800b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentDataInputGraphType.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs @@ -5,33 +5,30 @@ // 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.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) { - var displayName = field.DisplayName(); - 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) { @@ -40,23 +37,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents Name = partitionKey.EscapePartition(), ResolvedType = resolvedType, Resolver = null, - Description = field.RawProperties.Hints + Description = fieldInfo.Field.RawProperties.Hints }).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 { - Name = fieldName, + Name = fieldInfo.FieldName, ResolvedType = fieldGraphType, Resolver = null, - Description = $"The {displayName} field." - }).WithSourceName(field.Name); + Description = $"The {fieldInfo.DisplayName} field." + }).WithSourceName(fieldInfo); } } - Description = $"The structure of the {schemaName} data input type."; + Description = $"The structure of the {schemaInfo.DisplayName} data input type."; } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedGraphType.cs new file mode 100644 index 000000000..28b3e4445 --- /dev/null +++ b/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 + { + 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."; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs new file mode 100644 index 000000000..28834bcc7 --- /dev/null +++ b/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."; + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs index eb8536ea9..acea111da 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Extensions.cs @@ -12,6 +12,7 @@ using GraphQL; using GraphQL.Types; using GraphQL.Utilities; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.ObjectPool; 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(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(nameof(SchemaId)); + } + + public static FieldType WithSchemaNamedId(this FieldType field, SchemaInfo value) + { + return field.WithMetadata(nameof(SchemaNamedId), value.Schema.NamedId()); + } + + public static NamedId SchemaNamedId(this FieldType field) + { + return field.GetMetadata>(nameof(SchemaNamedId)); + } + + public static FieldType WithAppId(this FieldType field, NamedId value) + { + return field.WithMetadata(nameof(AppId), value); + } + + public static NamedId AppId(this FieldType field) + { + return field.GetMetadata>(nameof(AppId)); + } + + private static FieldType WithMetadata(this FieldType field, string key, object value) { if (field is MetadataProvider metadataProvider) { - metadataProvider.Metadata = new Dictionary + if (metadataProvider.Metadata is Dictionary dict) + { + dict[key] = value; + } + else { - ["sourceName"] = value - }; + metadataProvider.Metadata = new Dictionary + { + [key] = value + }; + } } return field; } - public static string GetSourceName(this FieldType field) - { - return field.GetMetadata("sourceName", string.Empty); - } - public static IGraphType Flatten(this QueryArgument type) { return type.ResolvedType.Flatten(); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldInputVisitor.cs similarity index 57% rename from backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs rename to backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldInputVisitor.cs index 9ddcabb73..72ba24d81 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldInputVisitor.cs @@ -7,98 +7,75 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; -using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed class InputFieldVisitor : IFieldVisitor + public sealed class GraphQLFieldInputVisitor : IFieldVisitor { - private static readonly InputFieldVisitor Instance = new InputFieldVisitor(); + private readonly GraphQLModel model; - public readonly struct Args - { - 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() + public GraphQLFieldInputVisitor(GraphQLModel model) { + this.model = model; } - public static IGraphType? Build(IField field, IGraphModel model, ISchemaEntity schema, string fieldName) - { - var args = new Args(model, schema, fieldName); - - return field.Accept(Instance, args); - } - - public IGraphType? Visit(IArrayField field, Args args) + public IGraphType? Visit(IArrayField field, FieldInfo args) { var schemaFieldType = new ListGraphType( new NonNullGraphType( - new NestedInputGraphType(args.Model, args.Schema, field, args.SchemaField))); + new NestedInputGraphType(model, args))); return schemaFieldType; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.References; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Boolean; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Date; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { - return GeolocationInputGraphType.Nullable; + return AllTypes.Json; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Json; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Float; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Json; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.String; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return AllTypes.Tags; } - public IGraphType? Visit(IField field, Args args) + public IGraphType? Visit(IField field, FieldInfo args) { return null; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLFieldVisitor.cs new file mode 100644 index 000000000..31194a82b --- /dev/null +++ b/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 field, FieldInfo args) + { + return (model.TypeFactory.AssetsList, Assets, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Boolean, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Date, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Json, Json, ContentActions.Json.Arguments); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Json, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Float, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return ResolveReferences(field, args); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.String, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (AllTypes.Tags, Noop, null); + } + + public (IGraphType?, IFieldResolver?, QueryArguments?) Visit(IField field, FieldInfo args) + { + return (null, null, null); + } + + private (IGraphType?, IFieldResolver?, QueryArguments?) ResolveReferences(IField 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, object?>((source, fieldContext, context) => + { + var key = fieldContext.FieldDefinition.SourceName(); + + if (source.TryGetValue(key, out var value)) + { + return valueResolver(value, fieldContext, context); + } + + return null; + }); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs deleted file mode 100644 index e0be3964a..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GraphQLTypeVisitor.cs +++ /dev/null @@ -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 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 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 field, Args args) - { - return ResolveAssets(); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopBoolean); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopDate); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopGeolocation); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopFloat); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveReferences(field, args); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopString); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return ResolveDefault(AllTypes.NoopTags); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField field, Args args) - { - return (null, null, null); - } - - public (IGraphType?, ValueResolver?, QueryArguments?) Visit(IField 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 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); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs deleted file mode 100644 index 2099792e7..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs +++ /dev/null @@ -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 - { - 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."; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs deleted file mode 100644 index 83acda2b1..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs +++ /dev/null @@ -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."; - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/Converters.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/Converters.cs index f31b46ed2..72eb23671 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/Converters.cs +++ b/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 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 { - 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)) { - result[field.GetSourceName()] = JsonConverter.ParseJson(value); + result[field.SourceName()] = JsonConverter.ParseJson(value); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs deleted file mode 100644 index 1e4f0e03e..000000000 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/GeolocationInputGraphType.cs +++ /dev/null @@ -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 - }); - } - } -} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SchemaInfos.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SchemaInfos.cs new file mode 100644 index 000000000..9d42acba6 --- /dev/null +++ b/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 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 Fields) + { + private static readonly IReadOnlyList EmptyFields = new List(); + + 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); + } + } +} \ No newline at end of file diff --git a/backend/src/Squidex.Web/ApiModelValidationAttribute.cs b/backend/src/Squidex.Web/ApiModelValidationAttribute.cs index 7260355cf..c5abf1801 100644 --- a/backend/src/Squidex.Web/ApiModelValidationAttribute.cs +++ b/backend/src/Squidex.Web/ApiModelValidationAttribute.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index 6424c414e..116571d10 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -221,7 +221,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL flatData = new { myString = "value", - myNumber = 1, + myNumber = 1.0, myBoolean = true, myDatetime = content.LastModified, myJsonValue = 1, @@ -244,12 +244,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { new { - nestedNumber = 10, + nestedNumber = 10.0, nestedBoolean = true }, new { - nestedNumber = 20, + nestedNumber = 20.0, nestedBoolean = false } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs index f08000c7c..73e974e3d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/TestContent.cs +++ b/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 { - iv = 22 + iv = 22.0 }, ["gql_2Numbers2"] = new { - iv = 23 + iv = 23.0 }, ["myString"] = new { @@ -208,11 +208,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }, ["myNumber"] = new { - iv = 1 + iv = 1.0 }, ["myNumber2"] = new { - iv = 2 + iv = 2.0 }, ["myBoolean"] = new { @@ -255,14 +255,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { new { - nestedNumber = 10, - nestedNumber2 = 11, + nestedNumber = 10.0, + nestedNumber2 = 11.0, nestedBoolean = true }, new { - nestedNumber = 20, - nestedNumber2 = 21, + nestedNumber = 20.0, + nestedNumber2 = 21.0, nestedBoolean = false } }