diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 186c63e18..6546572fe 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -22,117 +22,49 @@ using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using GraphQLSchema = GraphQL.Types.Schema; +#pragma warning disable IDE0003 + namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class GraphQLModel : IGraphModel { - private readonly Dictionary> fieldInfos; - private readonly Dictionary inputFieldInfos; + private readonly QueryGraphTypeVisitor schemaTypes; private readonly Dictionary contentTypes = new Dictionary(); private readonly Dictionary contentDataTypes = new Dictionary(); - private readonly Dictionary schemas; + private readonly Dictionary schemasById; private readonly PartitionResolver partitionResolver; private readonly IAppEntity app; - private readonly IGraphType assetListType; - private readonly IComplexGraphType assetType; + private readonly IGraphType assetType; private readonly GraphQLSchema graphQLSchema; - public bool CanGenerateAssetSourceUrl { get; } + public bool CanGenerateAssetSourceUrl { get; private set; } public GraphQLModel(IAppEntity app, IEnumerable schemas, IGraphQLUrlGenerator urlGenerator) { this.app = app; - CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl; - partitionResolver = app.PartitionResolver(); + CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl; + assetType = new AssetGraphType(this); - assetListType = new ListGraphType(new NonNullGraphType(assetType)); + schemasById = schemas.ToDictionary(x => x.Id); + schemaTypes = new QueryGraphTypeVisitor(GetContentType, new ListGraphType(new NonNullGraphType(assetType))); - inputFieldInfos = new Dictionary - { - { - typeof(StringFieldProperties), - AllTypes.String - }, - { - typeof(BooleanFieldProperties), - AllTypes.Boolean - }, - { - typeof(NumberFieldProperties), - AllTypes.Boolean - }, - { - typeof(DateTimeFieldProperties), - AllTypes.Date - }, - { - typeof(GeolocationFieldProperties), - AllTypes.GeolocationInput - }, - { - typeof(TagsFieldProperties), - AllTypes.ListOfNonNullString - }, - { - typeof(AssetsFieldProperties), - AllTypes.ListOfNonNullGuid - }, - { - typeof(ReferencesFieldProperties), - AllTypes.ListOfNonNullGuid - } - }; - - fieldInfos = new Dictionary> - { - { - typeof(StringFieldProperties), - field => ResolveDefault(AllTypes.NoopString) - }, - { - typeof(BooleanFieldProperties), - field => ResolveDefault(AllTypes.NoopBoolean) - }, - { - typeof(NumberFieldProperties), - field => ResolveDefault(AllTypes.NoopFloat) - }, - { - typeof(DateTimeFieldProperties), - field => ResolveDefault(AllTypes.NoopDate) - }, - { - typeof(JsonFieldProperties), - field => ResolveDefault(AllTypes.NoopJson) - }, - { - typeof(GeolocationFieldProperties), - field => ResolveDefault(AllTypes.NoopGeolocation) - }, - { - typeof(TagsFieldProperties), - field => ResolveDefault(AllTypes.NoopTags) - }, - { - typeof(AssetsFieldProperties), - field => ResolveAssets(assetListType) - }, - { - typeof(ReferencesFieldProperties), - field => ResolveReferences(field) - } - }; - - this.schemas = schemas.ToDictionary(x => x.Id); - - var m = new AppMutationsGraphType(this, this.schemas.Values); - var q = new AppQueriesGraphType(this, this.schemas.Values); - - graphQLSchema = new GraphQLSchema { Query = q, Mutation = m }; + graphQLSchema = BuildSchema(this); + + InitializeContentTypes(); + } + private static GraphQLSchema BuildSchema(GraphQLModel model) + { + var schemas = model.schemasById.Values; + + return new GraphQLSchema { Query = new AppQueriesGraphType(model, schemas), Mutation = new AppMutationsGraphType(model, schemas) }; + } + + private void InitializeContentTypes() + { foreach (var kvp in contentDataTypes) { kvp.Value.Initialize(this, kvp.Key); @@ -197,77 +129,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return resolver; } - private static ValueTuple ResolveAssets(IGraphType assetListType) - { - var resolver = new FuncFieldResolver(c => - { - var context = (GraphQLExecutionContext)c.UserContext; - var contentIds = c.Source.GetOrDefault(c.FieldName); - - return context.GetReferencedAssetsAsync(contentIds); - }); - - return (assetListType, resolver); - } - - private ValueTuple ResolveReferences(IField field) + public IFieldPartitioning ResolvePartition(Partitioning key) { - var schemaId = ((ReferencesFieldProperties)field.RawProperties).SchemaId; - - var contentType = GetContentType(schemaId); - - if (contentType == null) - { - return (null, null); - } - - var resolver = new FuncFieldResolver(c => - { - var context = (GraphQLExecutionContext)c.UserContext; - var contentIds = c.Source.GetOrDefault(c.FieldName); - - return context.GetReferencedContentsAsync(schemaId, contentIds); - }); - - var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType)); - - return (schemaFieldType, resolver); + return partitionResolver(key); } - public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) + public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field) { - Guard.NotNull(context, nameof(context)); - - var result = await new DocumentExecuter().ExecuteAsync(options => - { - options.Query = query.Query; - options.Schema = graphQLSchema; - options.Inputs = query.Variables?.ToInputs() ?? new Inputs(); - options.UserContext = context; - options.OperationName = query.OperationName; - }).ConfigureAwait(false); - - return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray()); + return field.Accept(schemaTypes); } - public IFieldPartitioning ResolvePartition(Partitioning key) + public IGraphType GetInputGraphType(IField field) { - return partitionResolver(key); + return field.GetInputGraphType(); } - public IComplexGraphType GetAssetType() + public IGraphType GetAssetType() { return assetType; } - public (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field) + public IGraphType GetContentDataType(Guid schemaId) { - return fieldInfos[field.RawProperties.GetType()](field); - } - - public IComplexGraphType GetContentDataType(Guid schemaId) - { - var schema = schemas.GetOrDefault(schemaId); + var schema = schemasById.GetOrDefault(schemaId); if (schema == null) { @@ -277,9 +161,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null; } - public IComplexGraphType GetContentType(Guid schemaId) + public IGraphType GetContentType(Guid schemaId) { - var schema = schemas.GetOrDefault(schemaId); + var schema = schemasById.GetOrDefault(schemaId); if (schema == null) { @@ -289,9 +173,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return contentTypes.GetOrAdd(schema, s => new ContentGraphType()); } - public IGraphType GetInputGraphType(IField field) + public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) { - return inputFieldInfos.GetOrAddDefault(field.RawProperties.GetType()); + Guard.NotNull(context, nameof(context)); + + var result = await new DocumentExecuter().ExecuteAsync(options => + { + options.Inputs = query.Variables?.ToInputs() ?? new Inputs(); + options.Query = query.Query; + options.OperationName = query.OperationName; + options.Schema = graphQLSchema; + options.UserContext = context; + }).ConfigureAwait(false); + + return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray()); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs index 7f7330cb9..91acea708 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs @@ -20,12 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL IFieldPartitioning ResolvePartition(Partitioning key); - IComplexGraphType GetAssetType(); - - IComplexGraphType GetContentType(Guid schemaId); - - IComplexGraphType GetContentDataType(Guid schemaId); - IFieldResolver ResolveAssetUrl(); IFieldResolver ResolveAssetSourceUrl(); @@ -34,6 +28,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL IFieldResolver ResolveContentUrl(ISchemaEntity schema); + IGraphType GetAssetType(); + + IGraphType GetContentType(Guid schemaId); + + IGraphType GetContentDataType(Guid schemaId); + IGraphType GetInputGraphType(IField field); (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(IField field); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs index ff34568c9..00be09720 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs @@ -8,6 +8,7 @@ using System; using GraphQL.Types; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs index 58ec5d018..0da986ba6 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Description = "The app mutations."; } - private void AddContentCreate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType, IComplexGraphType contentType) + private void AddContentCreate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType contentDataType, IGraphType contentType) { AddField(new FieldType { @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) + private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType) { AddField(new FieldType { @@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) + private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType) { AddField(new FieldType { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs index fcc0f6110..3b97da85b 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddAssetsQueries(IComplexGraphType assetType) + private void AddAssetsQueries(IGraphType assetType) { AddField(new FieldType { @@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentQueries(Guid schemaId, string schemaType, string schemaName, IComplexGraphType contentType) + private void AddContentQueries(Guid schemaId, string schemaType, string schemaName, IGraphType contentType) { AddField(new FieldType { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs index b86685071..3597bf76c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs @@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class AssetsResultGraphType : ObjectGraphType> { - public AssetsResultGraphType(IComplexGraphType assetType) + public AssetsResultGraphType(IGraphType assetType) { Name = $"AssetResultDto"; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs index ebe00aadb..d050c3696 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs @@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentDataChangedResultGraphType : ObjectGraphType { - public ContentDataChangedResultGraphType(string schemaType, string schemaName, IComplexGraphType contentDataType) + public ContentDataChangedResultGraphType(string schemaType, string schemaName, IGraphType contentDataType) { Name = $"{schemaName}DataChangedResultDto"; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs index c3c052d06..d7ae791e0 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs @@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentsResultGraphType : ObjectGraphType> { - public ContentsResultGraphType(string schemaType, string schemaName, IComplexGraphType contentType) + public ContentsResultGraphType(string schemaType, string schemaName, IGraphType contentType) { Name = $"{schemaType}ResultDto"; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs new file mode 100644 index 000000000..84af9a8f0 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Types; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public static class InputFieldExtensions + { + public static IGraphType GetInputGraphType(this IField field) + { + return field.Accept(InputFieldVisitor.Default); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs new file mode 100644 index 000000000..80ea21c4c --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Types; +using Squidex.Domain.Apps.Core.Schemas; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class InputFieldVisitor : IFieldVisitor + { + public static readonly InputFieldVisitor Default = new InputFieldVisitor(); + + private InputFieldVisitor() + { + } + + public IGraphType Visit(IField field) + { + return AllTypes.ListOfNonNullGuid; + } + + public IGraphType Visit(IField field) + { + return AllTypes.Boolean; + } + + public IGraphType Visit(IField field) + { + return AllTypes.Date; + } + + public IGraphType Visit(IField field) + { + return AllTypes.GeolocationInput; + } + + public IGraphType Visit(IField field) + { + return AllTypes.NoopJson; + } + + public IGraphType Visit(IField field) + { + return AllTypes.Float; + } + + public IGraphType Visit(IField field) + { + return AllTypes.ListOfNonNullGuid; + } + + public IGraphType Visit(IField field) + { + return AllTypes.String; + } + + public IGraphType Visit(IField field) + { + return AllTypes.ListOfNonNullString; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs new file mode 100644 index 000000000..4ca4bae5a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs @@ -0,0 +1,115 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using GraphQL.Resolvers; +using GraphQL.Types; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class QueryGraphTypeVisitor : IFieldVisitor<(IGraphType ResolveType, IFieldResolver Resolver)> + { + private readonly Func schemaResolver; + private readonly IGraphType assetListType; + + public QueryGraphTypeVisitor(Func schemaResolver, IGraphType assetListType) + { + this.assetListType = assetListType; + this.schemaResolver = schemaResolver; + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveAssets(assetListType); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopBoolean); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopDate); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopGeolocation); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopJson); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopFloat); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveReferences(field); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopString); + } + + public (IGraphType ResolveType, IFieldResolver Resolver) Visit(IField field) + { + return ResolveDefault(AllTypes.NoopTags); + } + + private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(IGraphType type) + { + return (type, new FuncFieldResolver(c => c.Source.GetOrDefault(c.FieldName))); + } + + private static ValueTuple ResolveAssets(IGraphType assetListType) + { + var resolver = new FuncFieldResolver(c => + { + var context = (GraphQLExecutionContext)c.UserContext; + var contentIds = c.Source.GetOrDefault(c.FieldName); + + return context.GetReferencedAssetsAsync(contentIds); + }); + + return (assetListType, resolver); + } + + private ValueTuple ResolveReferences(IField field) + { + var schemaId = ((ReferencesFieldProperties)field.RawProperties).SchemaId; + + var contentType = schemaResolver(schemaId); + + if (contentType == null) + { + return (null, null); + } + + var resolver = new FuncFieldResolver(c => + { + var context = (GraphQLExecutionContext)c.UserContext; + var contentIds = c.Source.GetOrDefault(c.FieldName); + + return context.GetReferencedContentsAsync(schemaId, contentIds); + }); + + var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType)); + + return (schemaFieldType, resolver); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/NoopGraphType.cs similarity index 93% rename from src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/NoopGraphType.cs index 401521ea5..5be62090f 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/NoopGraphType.cs @@ -9,7 +9,7 @@ using System; using GraphQL.Language.AST; using GraphQL.Types; -namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils { public sealed class NoopGraphType : ScalarGraphType {