From 6e1e87365671392ca331c28bbd8b747a2a6590f9 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 01:23:05 +0100 Subject: [PATCH 1/7] Mutation endpoints started --- .../Contents/MongoContentRepository.cs | 1 - .../Contents/Commands/ContentCommand.cs | 3 - .../Contents/ContentEntity.cs | 56 +++ .../Contents/ContentQueryService.cs | 21 +- .../Contents/GraphQL/CachingGraphQLService.cs | 12 +- ...yContext.cs => GraphQLExecutionContext.cs} | 9 +- .../Contents/GraphQL/GraphQLModel.cs | 115 +++++-- .../{IGraphQLContext.cs => IGraphModel.cs} | 10 +- .../GraphQL/Types/AppMutationsGraphType.cs | 318 ++++++++++++++++++ .../GraphQL/Types/AppQueriesGraphType.cs | 107 +++--- .../Contents/GraphQL/Types/AssetGraphType.cs | 40 +-- ...tGraphType.cs => AssetsResultGraphType.cs} | 14 +- .../GraphQL/Types/CommandVersionGraphType.cs | 44 +++ .../Types/ContentDataGraphInputType.cs | 73 ++++ .../GraphQL/Types/ContentDataGraphType.cs | 17 +- .../GraphQL/Types/ContentGraphType.cs | 41 +-- ...raphType.cs => ContentsResultGraphType.cs} | 13 +- .../Types/GeolocationInputGraphType.cs | 31 ++ .../Contents/GraphQL/Types/GuidGraphType.cs | 55 +++ .../Schemas/SchemaExtensions.cs | 41 +++ .../SquidexCommand.cs | 3 + .../MongoDb/MongoRepositoryBase.cs | 1 - .../Controllers/Assets/AssetsController.cs | 8 +- .../Controllers/Content/ContentsController.cs | 18 +- .../Generator/SchemaSwaggerGenerator.cs | 35 +- .../Models/{AssetsDto.cs => ContentsDto.cs} | 2 +- .../EnrichWithActorCommandMiddleware.cs | 18 +- .../Contents/GraphQL/GraphQLMutationTests.cs | 207 ++++++++++++ ...GraphQLTests.cs => GraphQLQueriesTests.cs} | 167 +-------- .../Contents/GraphQL/GraphQLTestBase.cs | 169 ++++++++++ .../Contents/TestData/FakeContentEntity.cs | 35 -- 31 files changed, 1285 insertions(+), 399 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs rename src/Squidex.Domain.Apps.Entities/Contents/GraphQL/{GraphQLQueryContext.cs => GraphQLExecutionContext.cs} (84%) rename src/Squidex.Domain.Apps.Entities/Contents/GraphQL/{IGraphQLContext.cs => IGraphModel.cs} (80%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs rename src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/{AssetResultGraphType.cs => AssetsResultGraphType.cs} (70%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs rename src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/{ContentResultGraphType.cs => ContentsResultGraphType.cs} (77%) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GuidGraphType.cs create mode 100644 src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs rename src/Squidex/Areas/Api/Controllers/Content/Models/{AssetsDto.cs => ContentsDto.cs} (95%) create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs rename tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/{GraphQLTests.cs => GraphQLQueriesTests.cs} (77%) create mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs delete mode 100644 tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 0319305bc..eba2e0b88 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.OData.UriParser; -using MongoDB.Bson; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Apps; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs index 06b65cfc1..3629bdfdc 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs @@ -6,15 +6,12 @@ // ========================================================================== using System; -using System.Security.Claims; using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.Commands { public abstract class ContentCommand : SchemaCommand, IAggregateCommand { - public ClaimsPrincipal User { get; set; } - public Guid ContentId { get; set; } Guid IAggregateCommand.AggregateId diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs new file mode 100644 index 000000000..9c0bc7af7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEntity.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using NodaTime; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Contents +{ + public sealed class ContentEntity : IContentEntity + { + public Guid Id { get; set; } + + public Guid AppId { get; set; } + + public long Version { get; set; } + + public Instant Created { get; set; } + + public Instant LastModified { get; set; } + + public RefToken CreatedBy { get; set; } + + public RefToken LastModifiedBy { get; set; } + + public NamedContentData Data { get; set; } + + public Status Status { get; set; } + + public static ContentEntity Create(CreateContent command, EntityCreatedResult result) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + var response = new ContentEntity + { + Id = command.ContentId, + Data = result.IdOrValue, + Version = result.Version, + Created = now, + CreatedBy = command.Actor, + LastModified = now, + LastModifiedBy = command.Actor, + Status = command.Publish ? Status.Published : Status.Draft + }; + + return response; + } + } +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index ae01ee691..ebd65e858 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -12,7 +12,6 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.OData; using Microsoft.OData.UriParser; -using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Apps; @@ -122,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents foreach (var content in contents) { var contentData = scriptEngine.Transform(new ScriptContext { User = user, Data = content.Data, ContentId = content.Id }, scriptText); - var contentResult = SimpleMapper.Map(content, new Content()); + var contentResult = SimpleMapper.Map(content, new ContentEntity()); contentResult.Data = contentData; @@ -199,23 +198,5 @@ namespace Squidex.Domain.Apps.Entities.Contents return status; } - - private sealed class Content : IContentEntity - { - public Guid Id { get; set; } - public Guid AppId { get; set; } - - public long Version { get; set; } - - public Instant Created { get; set; } - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - public RefToken LastModifiedBy { get; set; } - - public NamedContentData Data { get; set; } - - public Status Status { get; set; } - } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index d970ca7e6..eb3d9f315 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Caching.Memory; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { @@ -20,6 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private readonly IContentQueryService contentQuery; + private readonly ICommandBus commandBus; private readonly IGraphQLUrlGenerator urlGenerator; private readonly IAssetRepository assetRepository; private readonly IAppProvider appProvider; @@ -27,17 +29,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL public CachingGraphQLService(IMemoryCache cache, IAppProvider appProvider, IAssetRepository assetRepository, + ICommandBus commandBus, IContentQueryService contentQuery, IGraphQLUrlGenerator urlGenerator) : base(cache) { Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(assetRepository, nameof(assetRepository)); - Guard.NotNull(contentQuery, nameof(urlGenerator)); + Guard.NotNull(commandBus, nameof(commandBus)); Guard.NotNull(contentQuery, nameof(contentQuery)); + Guard.NotNull(urlGenerator, nameof(urlGenerator)); this.appProvider = appProvider; this.assetRepository = assetRepository; + this.commandBus = commandBus; this.contentQuery = contentQuery; this.urlGenerator = urlGenerator; } @@ -53,9 +58,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } var modelContext = await GetModelAsync(app); - var queryContext = new GraphQLQueryContext(app, assetRepository, contentQuery, user, urlGenerator); - return await modelContext.ExecuteAsync(queryContext, query); + var ctx = new GraphQLExecutionContext(app, assetRepository, commandBus, contentQuery, user, urlGenerator); + + return await modelContext.ExecuteAsync(ctx, query); } private async Task GetModelAsync(IAppEntity app) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs similarity index 84% rename from src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index b050ab970..f73f3ab6d 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -13,17 +13,22 @@ using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public sealed class GraphQLQueryContext : QueryContext + public sealed class GraphQLExecutionContext : QueryContext { + public ICommandBus CommandBus { get; } + public IGraphQLUrlGenerator UrlGenerator { get; } - public GraphQLQueryContext(IAppEntity app, IAssetRepository assetRepository, IContentQueryService contentQuery, ClaimsPrincipal user, + public GraphQLExecutionContext(IAppEntity app, IAssetRepository assetRepository, ICommandBus commandBus, IContentQueryService contentQuery, ClaimsPrincipal user, IGraphQLUrlGenerator urlGenerator) : base(app, assetRepository, contentQuery, user) { + CommandBus = commandBus; + UrlGenerator = urlGenerator; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 8ac7242bf..0c8082a31 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -24,15 +24,17 @@ using GraphQLSchema = GraphQL.Types.Schema; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public sealed class GraphQLModel : IGraphQLContext + public sealed class GraphQLModel : IGraphModel { private readonly Dictionary> fieldInfos; - private readonly Dictionary schemaTypes = new Dictionary(); + private readonly Dictionary inputFieldInfos; + private readonly Dictionary contentTypes = new Dictionary(); + private readonly Dictionary contentDataTypes = new Dictionary(); private readonly Dictionary schemas; private readonly PartitionResolver partitionResolver; private readonly IAppEntity app; - private readonly IGraphType assetType; private readonly IGraphType assetListType; + private readonly IComplexGraphType assetType; private readonly GraphQLSchema graphQLSchema; public bool CanGenerateAssetSourceUrl { get; } @@ -48,6 +50,42 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL assetType = new AssetGraphType(this); assetListType = new ListGraphType(new NonNullGraphType(assetType)); + inputFieldInfos = new Dictionary + { + { + typeof(StringField), + new StringGraphType() + }, + { + typeof(BooleanField), + new BooleanGraphType() + }, + { + typeof(NumberField), + new FloatGraphType() + }, + { + typeof(DateTimeField), + new DateGraphType() + }, + { + typeof(GeolocationField), + new GeolocationInputGraphType() + }, + { + typeof(TagsField), + new ListGraphType(new StringGraphType()) + }, + { + typeof(AssetsField), + new ListGraphType(new GuidGraphType()) + }, + { + typeof(ReferencesField), + new ListGraphType(new GuidGraphType()) + } + }; + fieldInfos = new Dictionary> { { @@ -70,14 +108,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL typeof(JsonField), field => ResolveDefault("Json") }, - { - typeof(TagsField), - field => ResolveDefault("String") - }, { typeof(GeolocationField), field => ResolveDefault("Geolocation") }, + { + typeof(TagsField), + field => ResolveDefault("String") + }, { typeof(AssetsField), field => ResolveAssets(assetListType) @@ -90,11 +128,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL this.schemas = schemas.ToDictionary(x => x.Id); - graphQLSchema = new GraphQLSchema { Query = new AppQueriesGraphType(this, this.schemas.Values) }; + var m = new AppMutationsGraphType(this, this.schemas.Values); + var q = new AppQueriesGraphType(this, this.schemas.Values); + + graphQLSchema = new GraphQLSchema { Query = q, Mutation = m }; - foreach (var schemaType in schemaTypes.Values) + foreach (var kvp in contentDataTypes) { - schemaType.Initialize(); + kvp.Value.Initialize(this, kvp.Key); + } + + foreach (var kvp in contentTypes) + { + kvp.Value.Initialize(this, kvp.Key, contentDataTypes[kvp.Key]); } } @@ -107,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; return context.UrlGenerator.GenerateAssetUrl(app, c.Source); }); @@ -119,7 +165,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; return context.UrlGenerator.GenerateAssetSourceUrl(app, c.Source); }); @@ -131,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; return context.UrlGenerator.GenerateAssetThumbnailUrl(app, c.Source); }); @@ -143,7 +189,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; return context.UrlGenerator.GenerateContentUrl(app, schema, c.Source); }); @@ -155,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; var contentIds = c.Source.GetOrDefault(c.FieldName); return context.GetReferencedAssetsAsync(contentIds); @@ -167,27 +213,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL private ValueTuple ResolveReferences(Field field) { var schemaId = ((ReferencesField)field).Properties.SchemaId; - var schemaType = GetSchemaType(schemaId); - if (schemaType == null) + var contentType = GetContentType(schemaId); + + if (contentType == null) { return (null, null); } var resolver = new FuncFieldResolver(c => { - var context = (GraphQLQueryContext)c.UserContext; + var context = (GraphQLExecutionContext)c.UserContext; var contentIds = c.Source.GetOrDefault(c.FieldName); return context.GetReferencedContentsAsync(schemaId, contentIds); }); - var schemaFieldType = new ListGraphType(new NonNullGraphType(GetSchemaType(schemaId))); + var schemaFieldType = new ListGraphType(new NonNullGraphType(contentType)); return (schemaFieldType, resolver); } - public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLQueryContext context, GraphQLQuery query) + public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query) { Guard.NotNull(context, nameof(context)); @@ -208,7 +255,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return partitionResolver(key); } - public IGraphType GetAssetType() + public IComplexGraphType GetAssetType() { return assetType; } @@ -218,11 +265,33 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return fieldInfos[field.GetType()](field); } - public IGraphType GetSchemaType(Guid schemaId) + public IComplexGraphType GetContentDataType(Guid schemaId) { var schema = schemas.GetOrDefault(schemaId); - return schema != null ? schemaTypes.GetOrAdd(schemaId, k => new ContentGraphType(schema, this)) : null; + if (schema == null) + { + return null; + } + + return schema != null ? contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType()) : null; + } + + public IComplexGraphType GetContentType(Guid schemaId) + { + var schema = schemas.GetOrDefault(schemaId); + + if (schema == null) + { + return null; + } + + return contentTypes.GetOrAdd(schema, s => new ContentGraphType()); + } + + public IGraphType GetInputGraphType(Field field) + { + return inputFieldInfos.GetOrAddDefault(field.GetType()); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs similarity index 80% rename from src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs index 45e00873a..29834fa71 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs @@ -14,15 +14,17 @@ using Squidex.Domain.Apps.Entities.Schemas; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public interface IGraphQLContext + public interface IGraphModel { bool CanGenerateAssetSourceUrl { get; } IFieldPartitioning ResolvePartition(Partitioning key); - IGraphType GetAssetType(); + IComplexGraphType GetAssetType(); - IGraphType GetSchemaType(Guid schemaId); + IComplexGraphType GetContentType(Guid schemaId); + + IComplexGraphType GetContentDataType(Guid schemaId); IFieldResolver ResolveAssetUrl(); @@ -32,6 +34,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL IFieldResolver ResolveContentUrl(ISchemaEntity schema); + IGraphType GetInputGraphType(Field field); + (IGraphType ResolveType, IFieldResolver Resolver) GetGraphType(Field field); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs new file mode 100644 index 000000000..dc3ac8a4e --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -0,0 +1,318 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GraphQL.Resolvers; +using GraphQL.Types; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class AppMutationsGraphType : ObjectGraphType + { + public AppMutationsGraphType(IGraphModel model, IEnumerable schemas) + { + foreach (var schema in schemas) + { + var schemaId = schema.NamedId(); + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); + + var contentType = model.GetContentType(schema.Id); + var contentDataType = model.GetContentDataType(schema.Id); + + var inputType = new ContentDataGraphInputType(model, schema); + + AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType); + AddContentUpdate(schemaId, schemaType, schemaName, inputType, contentDataType); + AddContentPatch(schemaId, schemaType, schemaName, inputType, contentDataType); + AddContentPublish(schemaId, schemaType, schemaName); + AddContentUnpublish(schemaId, schemaType, schemaName); + AddContentArchive(schemaId, schemaType, schemaName); + AddContentRestore(schemaId, schemaType, schemaName); + AddContentDelete(schemaId, schemaType, schemaName); + } + + Description = "The app mutations."; + } + + private void AddContentCreate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType, IComplexGraphType contentType) + { + AddField(new FieldType + { + Name = $"create{schemaType}Content", + Arguments = new QueryArguments + { + new QueryArgument(typeof(BooleanGraphType)) + { + Name = "publish", + Description = "Set to true to autopublish content.", + DefaultValue = false + }, + new QueryArgument(typeof(NoopGraphType)) + { + Name = "data", + Description = $"The data for the {schemaName} content.", + DefaultValue = null, + ResolvedType = new NonNullGraphType(inputType), + }, + new QueryArgument(typeof(IntGraphType)) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any + } + }, + ResolvedType = new NonNullGraphType(contentType), + Resolver = ResolveAsync(async (c, publish) => + { + var argPublish = c.GetArgument("publish"); + + var contentData = GetContentData(c); + + var command = new CreateContent { SchemaId = schemaId, ContentId = Guid.NewGuid(), Data = contentData, Publish = argPublish }; + var commandContext = await publish(command); + + var result = commandContext.Result>(); + var response = ContentEntity.Create(command, result); + + return ContentEntity.Create(command, result); + }), + Description = $"Creates an {schemaName} content." + }); + } + + private void AddContentUpdate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType) + { + AddField(new FieldType + { + Name = $"update{schemaType}Content", + Arguments = new QueryArguments + { + new QueryArgument(typeof(NonNullGraphType)) + { + Name = "id", + Description = $"The id of the {schemaName} content (GUID)", + DefaultValue = string.Empty + }, + new QueryArgument(typeof(NoopGraphType)) + { + Name = "data", + Description = $"The data for the {schemaName} content.", + DefaultValue = null, + ResolvedType = new NonNullGraphType(inputType), + }, + new QueryArgument(typeof(IntGraphType)) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any + } + }, + ResolvedType = new NonNullGraphType(contentDataType), + Resolver = ResolveAsync(async (c, publish) => + { + var contentId = c.GetArgument("id"); + var contentData = GetContentData(c); + + var command = new UpdateContent { SchemaId = schemaId, ContentId = contentId, Data = contentData }; + var commandContext = await publish(command); + + var result = commandContext.Result(); + + return result.Data; + }), + Description = $"Update an {schemaName} content by id." + }); + } + + private void AddContentPatch(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType) + { + AddField(new FieldType + { + Name = $"patch{schemaType}Content", + Arguments = new QueryArguments + { + new QueryArgument(typeof(NonNullGraphType)) + { + Name = "id", + Description = $"The id of the {schemaName} content (GUID)", + DefaultValue = string.Empty + }, + new QueryArgument(typeof(NoopGraphType)) + { + Name = "data", + Description = $"The data for the {schemaName} content.", + DefaultValue = null, + ResolvedType = new NonNullGraphType(inputType), + }, + new QueryArgument(typeof(IntGraphType)) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any + } + }, + ResolvedType = new NonNullGraphType(contentDataType), + Resolver = ResolveAsync(async (c, publish) => + { + var contentId = c.GetArgument("id"); + var contentData = GetContentData(c); + + var command = new PatchContent { SchemaId = schemaId, ContentId = contentId, Data = contentData }; + var commandContext = await publish(command); + + var result = commandContext.Result(); + + return result.Data; + }), + Description = $"Patch a {schemaName} content." + }); + } + + private void AddContentPublish(NamedId schemaId, string schemaType, string schemaName) + { + AddField(new FieldType + { + Name = $"publish{schemaType}Content", + Arguments = CreateIdArguments(schemaName), + ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + Resolver = ResolveAsync((c, publish) => + { + var contentId = c.GetArgument("id"); + + var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Published }; + + return publish(command); + }), + Description = $"Publish a {schemaName} content." + }); + } + + private void AddContentUnpublish(NamedId schemaId, string schemaType, string schemaName) + { + AddField(new FieldType + { + Name = $"unpublish{schemaType}Content", + Arguments = CreateIdArguments(schemaName), + ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + Resolver = ResolveAsync((c, publish) => + { + var contentId = c.GetArgument("id"); + + var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; + + return publish(command); + }), + Description = $"Unpublish a {schemaName} content." + }); + } + + private void AddContentArchive(NamedId schemaId, string schemaType, string schemaName) + { + AddField(new FieldType + { + Name = $"archive{schemaType}Content", + Arguments = CreateIdArguments(schemaName), + ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + Resolver = ResolveAsync((c, publish) => + { + var contentId = c.GetArgument("id"); + + var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Archived }; + + return publish(command); + }), + Description = $"Archive a {schemaName} content." + }); + } + + private void AddContentRestore(NamedId schemaId, string schemaType, string schemaName) + { + AddField(new FieldType + { + Name = $"restore{schemaType}Content", + Arguments = CreateIdArguments(schemaName), + ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + Resolver = ResolveAsync((c, publish) => + { + var contentId = c.GetArgument("id"); + + var command = new ChangeContentStatus { SchemaId = schemaId, ContentId = contentId, Status = Status.Draft }; + + return publish(command); + }), + Description = $"Restore a {schemaName} content." + }); + } + + private void AddContentDelete(NamedId schemaId, string schemaType, string schemaName) + { + AddField(new FieldType + { + Name = $"delete{schemaType}Content", + Arguments = CreateIdArguments(schemaName), + ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + Resolver = ResolveAsync((c, publish) => + { + var contentId = c.GetArgument("id"); + + var command = new DeleteContent { SchemaId = schemaId, ContentId = contentId }; + + return publish(command); + }), + Description = $"Delete an {schemaName} content." + }); + } + + private static QueryArguments CreateIdArguments(string schemaName) + { + return new QueryArguments + { + new QueryArgument(typeof(GuidGraphType)) + { + Name = "id", + Description = $"The id of the {schemaName} content (GUID)", + DefaultValue = string.Empty + }, + new QueryArgument(typeof(IntGraphType)) + { + Name = "expectedVersion", + Description = "The expected version", + DefaultValue = EtagVersion.Any + } + }; + } + + private static IFieldResolver ResolveAsync(Func>, Task> action) + { + return new FuncFieldResolver>(c => + { + var e = (GraphQLExecutionContext)c.UserContext; + + return action(c, command => + { + command.ExpectedVersion = c.GetArgument("expectedVersion"); + + return e.CommandBus.PublishAsync(command); + }); + }); + } + + private static NamedContentData GetContentData(ResolveFieldContext c) + { + return JObject.FromObject(c.GetArgument("data")).ToObject(); + } + } +} 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 393dc904b..06734c014 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs @@ -8,29 +8,32 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class AppQueriesGraphType : ObjectGraphType { - public AppQueriesGraphType(IGraphQLContext ctx, IEnumerable schemas) + public AppQueriesGraphType(IGraphModel model, IEnumerable schemas) { - var assetType = ctx.GetAssetType(); + var assetType = model.GetAssetType(); AddAssetFind(assetType); AddAssetsQueries(assetType); foreach (var schema in schemas) { - var schemaName = schema.SchemaDef.Properties.Label.WithFallback(schema.SchemaDef.Name); - var schemaType = ctx.GetSchemaType(schema.Id); + var schemaId = schema.Id; + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); - AddContentFind(schema, schemaType, schemaName); - AddContentQueries(ctx, schema, schemaType, schemaName); + var contentType = model.GetContentType(schema.Id); + + AddContentFind(schemaId, schemaType, schemaName, contentType); + AddContentQueries(schemaId, schemaType, schemaName, contentType); } Description = "The app queries."; @@ -43,102 +46,94 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = "findAsset", Arguments = CreateAssetFindArguments(), ResolvedType = assetType, - Resolver = new FuncFieldResolver(c => + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; - var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString())); + var assetId = c.GetArgument("id"); - return context.FindAssetAsync(contentId); + return e.FindAssetAsync(assetId); }), Description = "Find an asset by id." }); } - private void AddContentFind(ISchemaEntity schema, IGraphType schemaType, string schemaName) + private void AddContentFind(Guid schemaId, string schemaType, string schemaName, IGraphType contentType) { AddField(new FieldType { - Name = $"find{schema.Name.ToPascalCase()}Content", + Name = $"find{schemaType}Content", Arguments = CreateContentFindTypes(schemaName), - ResolvedType = schemaType, - Resolver = new FuncFieldResolver(c => + ResolvedType = contentType, + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; - var contentId = Guid.Parse(c.GetArgument("id", Guid.Empty.ToString())); + var contentId = c.GetArgument("id"); - return context.FindContentAsync(schema.Id, contentId); + return e.FindContentAsync(schemaId, contentId); }), Description = $"Find an {schemaName} content by id." }); } - private void AddAssetsQueries(IGraphType assetType) + private void AddAssetsQueries(IComplexGraphType assetType) { AddField(new FieldType { Name = "queryAssets", Arguments = CreateAssetQueryArguments(), ResolvedType = new ListGraphType(new NonNullGraphType(assetType)), - Resolver = new FuncFieldResolver(c => + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; - - var argTop = c.GetArgument("top", 20); + var argTake = c.GetArgument("take", 20); var argSkip = c.GetArgument("skip", 0); var argQuery = c.GetArgument("search", string.Empty); - return context.QueryAssetsAsync(argQuery, argSkip, argTop); + return e.QueryAssetsAsync(argQuery, argSkip, argTake); }), - Description = "Query assets items." + Description = "Get assets." }); AddField(new FieldType { Name = "queryAssetsWithTotal", Arguments = CreateAssetQueryArguments(), - ResolvedType = new AssetResultGraphType(assetType), - Resolver = new FuncFieldResolver(c => + ResolvedType = new AssetsResultGraphType(assetType), + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; - - var argTop = c.GetArgument("top", 20); + var argTake = c.GetArgument("take", 20); var argSkip = c.GetArgument("skip", 0); var argQuery = c.GetArgument("search", string.Empty); - return context.QueryAssetsAsync(argQuery, argSkip, argTop); + return e.QueryAssetsAsync(argQuery, argSkip, argTake); }), - Description = "Query assets items with total count." + Description = "Get assets and total count." }); } - private void AddContentQueries(IGraphQLContext ctx, ISchemaEntity schema, IGraphType schemaType, string schemaName) + private void AddContentQueries(Guid schemaId, string schemaType, string schemaName, IComplexGraphType contentType) { AddField(new FieldType { - Name = $"query{schema.Name.ToPascalCase()}Contents", + Name = $"query{schemaType}Contents", Arguments = CreateContentQueryArguments(), - ResolvedType = new ListGraphType(new NonNullGraphType(schemaType)), - Resolver = new FuncFieldResolver(c => + ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; var contentQuery = BuildODataQuery(c); - return context.QueryContentsAsync(schema.Id.ToString(), contentQuery); + return e.QueryContentsAsync(schemaId.ToString(), contentQuery); }), Description = $"Query {schemaName} content items." }); AddField(new FieldType { - Name = $"query{schema.Name.ToPascalCase()}ContentsWithTotal", + Name = $"query{schemaType}ContentsWithTotal", Arguments = CreateContentQueryArguments(), - ResolvedType = new ContentResultGraphType(ctx, schema, schemaName), - Resolver = new FuncFieldResolver(c => + ResolvedType = new ContentsResultGraphType(schemaType, schemaName, contentType), + Resolver = ResolveAsync((c, e) => { - var context = (GraphQLQueryContext)c.UserContext; var contentQuery = BuildODataQuery(c); - return context.QueryContentsAsync(schema.Id.ToString(), contentQuery); + return e.QueryContentsAsync(schemaId.ToString(), contentQuery); }), Description = $"Query {schemaName} content items with total count." }); @@ -148,10 +143,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(typeof(NonNullGraphType)) { Name = "id", - Description = "The id of the asset.", + Description = "The id of the asset (GUID).", DefaultValue = string.Empty } }; @@ -161,10 +156,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(typeof(NonNullGraphType)) { Name = "id", - Description = $"The id of the {schemaName} content.", + Description = $"The id of the {schemaName} content (GUID)", DefaultValue = string.Empty } }; @@ -176,8 +171,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { new QueryArgument(typeof(IntGraphType)) { - Name = "top", - Description = "Optional number of assets to take.", + Name = "take", + Description = "Optional number of assets to take (Default: 20).", DefaultValue = 20 }, new QueryArgument(typeof(IntGraphType)) @@ -189,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(typeof(StringGraphType)) { Name = "search", - Description = "Optional query.", + Description = "Optional query to limit the files by name.", DefaultValue = string.Empty } }; @@ -202,7 +197,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types new QueryArgument(typeof(IntGraphType)) { Name = "top", - Description = "Optional number of contents to take.", + Description = "Optional number of contents to take (Default: 20).", DefaultValue = 20 }, new QueryArgument(typeof(IntGraphType)) @@ -242,5 +237,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return odataQuery; } + + private static IFieldResolver ResolveAsync(Func> action) + { + return new FuncFieldResolver>(c => + { + var e = (GraphQLExecutionContext)c.UserContext; + + return action(c, e); + }); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 7795a5a62..9c2ade9ab 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -15,145 +15,145 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class AssetGraphType : ObjectGraphType { - public AssetGraphType(IGraphQLContext context) + public AssetGraphType(IGraphModel model) { Name = "AssetDto"; AddField(new FieldType { Name = "id", - Resolver = Resolver(x => x.Id.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.Id.ToString()), Description = "The id of the asset." }); AddField(new FieldType { Name = "version", - Resolver = Resolver(x => x.Version), ResolvedType = new NonNullGraphType(new IntGraphType()), + Resolver = Resolve(x => x.Version), Description = "The version of the asset." }); AddField(new FieldType { Name = "created", - Resolver = Resolver(x => x.Created.ToDateTimeUtc()), ResolvedType = new NonNullGraphType(new DateGraphType()), + Resolver = Resolve(x => x.Created.ToDateTimeUtc()), Description = "The date and time when the asset has been created." }); AddField(new FieldType { Name = "createdBy", - Resolver = Resolver(x => x.CreatedBy.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.CreatedBy.ToString()), Description = "The user that has created the asset." }); AddField(new FieldType { Name = "lastModified", - Resolver = Resolver(x => x.LastModified.ToDateTimeUtc()), ResolvedType = new NonNullGraphType(new DateGraphType()), + Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), Description = "The date and time when the asset has been modified last." }); AddField(new FieldType { Name = "lastModifiedBy", - Resolver = Resolver(x => x.LastModifiedBy.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.LastModifiedBy.ToString()), Description = "The user that has updated the asset last." }); AddField(new FieldType { Name = "mimeType", - Resolver = Resolver(x => x.MimeType), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.MimeType), Description = "The mime type." }); AddField(new FieldType { Name = "url", - Resolver = context.ResolveAssetUrl(), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = model.ResolveAssetUrl(), Description = "The url to the asset." }); AddField(new FieldType { Name = "thumbnailUrl", - Resolver = context.ResolveAssetThumbnailUrl(), ResolvedType = new StringGraphType(), + Resolver = model.ResolveAssetThumbnailUrl(), Description = "The thumbnail url to the asset." }); AddField(new FieldType { Name = "fileName", - Resolver = Resolver(x => x.FileName), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.FileName), Description = "The file name." }); AddField(new FieldType { Name = "fileType", - Resolver = Resolver(x => x.FileName.FileType()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.FileName.FileType()), Description = "The file type." }); AddField(new FieldType { Name = "fileSize", - Resolver = Resolver(x => x.FileSize), ResolvedType = new NonNullGraphType(new IntGraphType()), + Resolver = Resolve(x => x.FileSize), Description = "The size of the file in bytes." }); AddField(new FieldType { Name = "fileVersion", - Resolver = Resolver(x => x.FileVersion), ResolvedType = new NonNullGraphType(new IntGraphType()), + Resolver = Resolve(x => x.FileVersion), Description = "The version of the file." }); AddField(new FieldType { Name = "isImage", - Resolver = Resolver(x => x.IsImage), ResolvedType = new NonNullGraphType(new BooleanGraphType()), + Resolver = Resolve(x => x.IsImage), Description = "Determines of the created file is an image." }); AddField(new FieldType { Name = "pixelWidth", - Resolver = Resolver(x => x.PixelWidth), ResolvedType = new IntGraphType(), + Resolver = Resolve(x => x.PixelWidth), Description = "The width of the image in pixels if the asset is an image." }); AddField(new FieldType { Name = "pixelHeight", - Resolver = Resolver(x => x.PixelHeight), ResolvedType = new IntGraphType(), + Resolver = Resolve(x => x.PixelHeight), Description = "The height of the image in pixels if the asset is an image." }); - if (context.CanGenerateAssetSourceUrl) + if (model.CanGenerateAssetSourceUrl) { AddField(new FieldType { Name = "sourceUrl", - Resolver = context.ResolveAssetSourceUrl(), ResolvedType = new StringGraphType(), + Resolver = model.ResolveAssetSourceUrl(), Description = "The source url of the asset." }); } @@ -161,7 +161,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Description = "An asset"; } - private static IFieldResolver Resolver(Func action) + private static IFieldResolver Resolve(Func action) { return new FuncFieldResolver(c => action(c.Source)); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs similarity index 70% rename from src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetResultGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs index d38478a33..1114e70a3 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs @@ -13,30 +13,32 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed class AssetResultGraphType : ObjectGraphType> + public sealed class AssetsResultGraphType : ObjectGraphType> { - public AssetResultGraphType(IGraphType assetType) + public AssetsResultGraphType(IComplexGraphType assetType) { Name = $"AssetResultDto"; AddField(new FieldType { Name = "total", - Resolver = Resolver(x => x.Total), + Resolver = Resolve(x => x.Total), ResolvedType = new NonNullGraphType(new IntGraphType()), - Description = $"The total number of asset." + Description = $"The total count of assets." }); AddField(new FieldType { Name = "items", - Resolver = Resolver(x => x), + Resolver = Resolve(x => x), ResolvedType = new ListGraphType(new NonNullGraphType(assetType)), Description = $"The assets." }); + + Description = "List of assets and total count of assets."; } - private static IFieldResolver Resolver(Func, object> action) + private static IFieldResolver Resolve(Func, object> action) { return new FuncFieldResolver, object>(c => action(c.Source)); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs new file mode 100644 index 000000000..30fb044ea --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Resolvers; +using GraphQL.Types; +using Squidex.Infrastructure.Commands; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class CommandVersionGraphType : ComplexGraphType + { + public CommandVersionGraphType() + { + Name = "CommandVersionDto"; + + AddField(new FieldType + { + Name = "version", + ResolvedType = new IntGraphType(), + Resolver = ResolveEtag(), + Description = "The new version of the item." + }); + + Description = "The result of a mutation"; + } + + private static IFieldResolver ResolveEtag() + { + return new FuncFieldResolver(x => + { + if (x.Source.Result() is EntitySavedResult result) + { + return (int)result.Version; + } + + return null; + }); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs new file mode 100644 index 000000000..440ea753a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs @@ -0,0 +1,73 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Linq; +using GraphQL.Resolvers; +using GraphQL.Types; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class ContentDataGraphInputType : InputObjectGraphType + { + public ContentDataGraphInputType(IGraphModel model, ISchemaEntity schema) + { + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); + + Name = $"{schemaType}InputDto"; + + foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden)) + { + var inputType = model.GetInputGraphType(field); + + if (inputType != null) + { + if (field.RawProperties.IsRequired) + { + inputType = new NonNullGraphType(inputType); + } + + var fieldName = field.RawProperties.Label.WithFallback(field.Name); + + var fieldGraphType = new InputObjectGraphType + { + Name = $"{schemaType}Data{field.Name.ToPascalCase()}InputDto" + }; + + var partition = model.ResolvePartition(field.Partitioning); + + foreach (var partitionItem in partition) + { + fieldGraphType.AddField(new FieldType + { + Name = partitionItem.Key, + ResolvedType = inputType, + Resolver = null, + Description = field.RawProperties.Hints + }); + } + + fieldGraphType.Description = $"The input structure of the {fieldName} of a {schemaName} content type."; + + var fieldResolver = new FuncFieldResolver(c => c.Source.GetOrDefault(field.Name)); + + AddField(new FieldType + { + Name = field.Name.ToCamelCase(), + Resolver = fieldResolver, + ResolvedType = fieldGraphType + }); + } + } + + Description = $"The structure of a {schemaName} content type."; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index f1eb1faba..b9822826b 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -9,22 +9,23 @@ using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -using Schema = Squidex.Domain.Apps.Core.Schemas.Schema; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentDataGraphType : ObjectGraphType { - public ContentDataGraphType(Schema schema, IGraphQLContext qlContext) + public void Initialize(IGraphModel model, ISchemaEntity schema) { - var schemaName = schema.Properties.Label.WithFallback(schema.Name); + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); - Name = $"{schema.Name.ToPascalCase()}DataDto"; + Name = $"{schemaType}DataDto"; - foreach (var field in schema.Fields.Where(x => !x.IsHidden)) + foreach (var field in schema.SchemaDef.Fields.Where(x => !x.IsHidden)) { - var fieldInfo = qlContext.GetGraphType(field); + var fieldInfo = model.GetGraphType(field); if (fieldInfo.ResolveType != null) { @@ -32,10 +33,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var fieldGraphType = new ObjectGraphType { - Name = $"{schema.Name.ToPascalCase()}Data{field.Name.ToPascalCase()}Dto" + Name = $"{schemaType}Data{field.Name.ToPascalCase()}Dto" }; - var partition = qlContext.ResolvePartition(field.Partitioning); + var partition = model.ResolvePartition(field.Partitioning); foreach (var partitionItem in partition) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index f17cfa1a0..b8dc4b304 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -10,92 +10,81 @@ using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentGraphType : ObjectGraphType { - private readonly ISchemaEntity schema; - private readonly IGraphQLContext ctx; - - public ContentGraphType(ISchemaEntity schema, IGraphQLContext ctx) + public void Initialize(IGraphModel model, ISchemaEntity schema, IComplexGraphType contentDataType) { - this.ctx = ctx; - this.schema = schema; + var schemaType = schema.TypeName(); + var schemaName = schema.DisplayName(); - Name = $"{schema.Name.ToPascalCase()}Dto"; - } - - public void Initialize() - { - var schemaName = schema.SchemaDef.Properties.Label.WithFallback(schema.Name); + Name = $"{schemaType}Dto"; AddField(new FieldType { Name = "id", - Resolver = Resolver(x => x.Id.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.Id.ToString()), Description = $"The id of the {schemaName} content." }); AddField(new FieldType { Name = "version", - Resolver = Resolver(x => x.Version), ResolvedType = new NonNullGraphType(new IntGraphType()), + Resolver = Resolve(x => x.Version), Description = $"The version of the {schemaName} content." }); AddField(new FieldType { Name = "created", - Resolver = Resolver(x => x.Created.ToDateTimeUtc()), ResolvedType = new NonNullGraphType(new DateGraphType()), + Resolver = Resolve(x => x.Created.ToDateTimeUtc()), Description = $"The date and time when the {schemaName} content has been created." }); AddField(new FieldType { Name = "createdBy", - Resolver = Resolver(x => x.CreatedBy.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.CreatedBy.ToString()), Description = $"The user that has created the {schemaName} content." }); AddField(new FieldType { Name = "lastModified", - Resolver = Resolver(x => x.LastModified.ToDateTimeUtc()), ResolvedType = new NonNullGraphType(new DateGraphType()), + Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), Description = $"The date and time when the {schemaName} content has been modified last." }); AddField(new FieldType { Name = "lastModifiedBy", - Resolver = Resolver(x => x.LastModifiedBy.ToString()), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = Resolve(x => x.LastModifiedBy.ToString()), Description = $"The user that has updated the {schemaName} content last." }); AddField(new FieldType { Name = "url", - Resolver = ctx.ResolveContentUrl(schema), ResolvedType = new NonNullGraphType(new StringGraphType()), + Resolver = model.ResolveContentUrl(schema), Description = $"The url to the the {schemaName} content." }); - var dataType = new ContentDataGraphType(schema.SchemaDef, ctx); - - if (dataType.Fields.Any()) + if (contentDataType.Fields.Any()) { AddField(new FieldType { Name = "data", - Resolver = Resolver(x => x.Data), - ResolvedType = new NonNullGraphType(dataType), + ResolvedType = new NonNullGraphType(contentDataType), + Resolver = Resolve(x => x.Data), Description = $"The data of the {schemaName} content." }); } @@ -103,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Description = $"The structure of a {schemaName} content type."; } - private static IFieldResolver Resolver(Func action) + private static IFieldResolver Resolve(Func action) { return new FuncFieldResolver(c => action(c.Source)); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs similarity index 77% rename from src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentResultGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs index 1b6ebee7f..c8992e252 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs @@ -8,18 +8,15 @@ using System; using GraphQL.Resolvers; using GraphQL.Types; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed class ContentResultGraphType : ObjectGraphType> + public sealed class ContentsResultGraphType : ObjectGraphType> { - public ContentResultGraphType(IGraphQLContext ctx, ISchemaEntity schema, string schemaName) + public ContentsResultGraphType(string schemaType, string schemaName, IComplexGraphType contentType) { - Name = $"{schema.Name.ToPascalCase()}ResultDto"; - - var schemaType = ctx.GetSchemaType(schema.Id); + Name = $"{schemaType}ResultDto"; AddField(new FieldType { @@ -33,9 +30,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "items", Resolver = Resolver(x => x), - ResolvedType = new ListGraphType(new NonNullGraphType(schemaType)), + ResolvedType = new ListGraphType(new NonNullGraphType(contentType)), Description = $"The {schemaName} items." }); + + Description = $"List of {schemaName} items and total count."; } private static IFieldResolver Resolver(Func, object> action) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs new file mode 100644 index 000000000..6de8c1405 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// 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 +{ + public sealed class GeolocationInputGraphType : InputObjectGraphType + { + public GeolocationInputGraphType() + { + Name = "GeolocationInputDto"; + + AddField(new FieldType + { + Name = "latitude", + ResolvedType = new NonNullGraphType(new FloatGraphType()) + }); + + AddField(new FieldType + { + Name = "longitude", + ResolvedType = new NonNullGraphType(new FloatGraphType()) + }); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GuidGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GuidGraphType.cs new file mode 100644 index 000000000..196c8ed44 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GuidGraphType.cs @@ -0,0 +1,55 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using GraphQL.Language.AST; +using GraphQL.Types; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class GuidGraphType : ScalarGraphType + { + public GuidGraphType() + { + Name = "Guid"; + + Description = "The `Guid` scalar type global unique identifier"; + } + + public override object Serialize(object value) + { + return ParseValue(value)?.ToString(); + } + + public override object ParseValue(object value) + { + if (value is Guid guid) + { + return guid; + } + + var inputValue = value?.ToString().Trim('"'); + + if (Guid.TryParse(inputValue, out guid)) + { + return guid; + } + + return null; + } + + public override object ParseLiteral(IValue value) + { + if (value is StringValue stringValue) + { + return ParseValue(stringValue.Value); + } + + return null; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs new file mode 100644 index 000000000..5d86e5682 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaExtensions.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Entities.Schemas +{ + public static class SchemaExtensions + { + public static NamedId NamedId(this ISchemaEntity schema) + { + return new NamedId(schema.Id, schema.Name); + } + + public static string TypeName(this ISchemaEntity schema) + { + return schema.SchemaDef.Name.ToPascalCase(); + } + + public static string DisplayName(this ISchemaEntity schema) + { + return schema.SchemaDef.Properties.Label.WithFallback(schema.TypeName()); + } + + public static string TypeName(this Schema schema) + { + return schema.Name.ToPascalCase(); + } + + public static string DisplayName(this Schema schema) + { + return schema.Properties.Label.WithFallback(schema.TypeName()); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs index a481289bf..a2c642e16 100644 --- a/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Security.Claims; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -14,6 +15,8 @@ namespace Squidex.Domain.Apps.Entities { public RefToken Actor { get; set; } + public ClaimsPrincipal User { get; set; } + public long ExpectedVersion { get; set; } } } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs index b05f80515..27cfdca13 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs @@ -8,7 +8,6 @@ using System; using System.Globalization; using System.Threading.Tasks; -using MongoDB.Bson; using MongoDB.Driver; using Squidex.Infrastructure.Tasks; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 0f2da0d0f..8a1d43c1f 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -60,9 +60,9 @@ namespace Squidex.Areas.Api.Controllers.Assets /// /// The name of the app. /// The optional asset ids. - /// The number of assets to skip. - /// The number of assets to take (Default: 20). - /// The query to limit the files by name. + /// Optional number of assets to skip. + /// Optional number of assets to take (Default: 20). + /// Optional query to limit the files by name. /// Comma separated list of mime types to get. /// /// 200 => Assets returned. @@ -76,7 +76,7 @@ namespace Squidex.Areas.Api.Controllers.Assets [Route("apps/{app}/assets/")] [ProducesResponseType(typeof(AssetsDto), 200)] [ApiCosts(1)] - public async Task GetAssets(string app, [FromQuery] string query = null, [FromQuery] string mimeTypes = null, [FromQuery] string ids = null, [FromQuery] int skip = 0, [FromQuery] int take = 10) + public async Task GetAssets(string app, [FromQuery] string query = null, [FromQuery] string mimeTypes = null, [FromQuery] string ids = null, [FromQuery] int skip = 0, [FromQuery] int take = 20) { var mimeTypeList = new HashSet(); diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index e9861fe66..fb8c80634 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -90,7 +90,7 @@ namespace Squidex.Areas.Api.Controllers.Contents await contentQuery.QueryAsync(App, name, User, archived, idsList) : await contentQuery.QueryAsync(App, name, User, archived, Request.QueryString.ToString()); - var response = new AssetsDto + var response = new ContentsDto { Total = result.Contents.Total, Items = result.Contents.Take(200).Select(item => @@ -161,7 +161,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new CreateContent { ContentId = Guid.NewGuid(), User = User, Data = request.ToCleaned(), Publish = publish }; + var command = new CreateContent { ContentId = Guid.NewGuid(), Data = request.ToCleaned(), Publish = publish }; var context = await CommandBus.PublishAsync(command); @@ -179,7 +179,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new UpdateContent { ContentId = id, User = User, Data = request.ToCleaned() }; + var command = new UpdateContent { ContentId = id, Data = request.ToCleaned() }; var context = await CommandBus.PublishAsync(command); @@ -197,7 +197,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new PatchContent { ContentId = id, User = User, Data = request.ToCleaned() }; + var command = new PatchContent { ContentId = id, Data = request.ToCleaned() }; var context = await CommandBus.PublishAsync(command); @@ -215,7 +215,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Published, ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Published, ContentId = id }; await CommandBus.PublishAsync(command); @@ -230,7 +230,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; await CommandBus.PublishAsync(command); @@ -245,7 +245,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Archived, ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Archived, ContentId = id }; await CommandBus.PublishAsync(command); @@ -260,7 +260,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id, User = User }; + var command = new ChangeContentStatus { Status = Status.Draft, ContentId = id }; await CommandBus.PublishAsync(command); @@ -275,7 +275,7 @@ namespace Squidex.Areas.Api.Controllers.Contents { await contentQuery.FindSchemaAsync(App, name); - var command = new DeleteContent { ContentId = id, User = User }; + var command = new DeleteContent { ContentId = id }; await CommandBus.PublishAsync(command); diff --git a/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemaSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemaSwaggerGenerator.cs index 9236c96d5..010f411c0 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemaSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemaSwaggerGenerator.cs @@ -14,6 +14,7 @@ using Squidex.Config; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.GenerateJsonSchema; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Pipeline.Swagger; using Squidex.Shared.Identity; @@ -32,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator private readonly JsonSchema4 dataSchema; private readonly string schemaPath; private readonly string schemaName; - private readonly string schemaKey; + private readonly string schemaType; private readonly string appPath; static SchemaSwaggerGenerator() @@ -68,12 +69,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator appPath = path; schemaPath = schema.Name; - schemaName = schema.Properties.Label.WithFallback(schema.Name); - schemaKey = schema.Name.ToPascalCase(); + schemaName = schema.DisplayName(); + schemaType = schema.TypeName(); - dataSchema = schemaResolver($"{schemaKey}Dto", schema.BuildJsonSchema(partitionResolver, schemaResolver)); + dataSchema = schemaResolver($"{schemaType}Dto", schema.BuildJsonSchema(partitionResolver, schemaResolver)); - contentSchema = schemaResolver($"{schemaKey}ContentDto", schemaBuilder.CreateContentSchema(schema, dataSchema)); + contentSchema = schemaResolver($"{schemaType}ContentDto", schemaBuilder.CreateContentSchema(schema, dataSchema)); } public void GenerateSchemaOperations() @@ -108,13 +109,13 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Get, null, $"{appPath}/{schemaPath}", operation => { - operation.OperationId = $"Query{schemaKey}Contents"; + operation.OperationId = $"Query{schemaType}Contents"; operation.Summary = $"Queries {schemaName} contents."; operation.Security = ReaderSecurity; operation.Description = SchemaQueryDescription; - operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take."); + operation.AddQueryParameter("$top", JsonObjectType.Number, "Optional number of contents to take (Default: 20)."); operation.AddQueryParameter("$skip", JsonObjectType.Number, "Optional number of contents to skip."); operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter."); operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search."); @@ -128,7 +129,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Get, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => { - operation.OperationId = $"Get{schemaKey}Content"; + operation.OperationId = $"Get{schemaType}Content"; operation.Summary = $"Get a {schemaName} content."; operation.Security = ReaderSecurity; @@ -140,7 +141,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Post, null, $"{appPath}/{schemaPath}", operation => { - operation.OperationId = $"Create{schemaKey}Content"; + operation.OperationId = $"Create{schemaType}Content"; operation.Summary = $"Create a {schemaName} content."; operation.Security = EditorSecurity; @@ -155,7 +156,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => { - operation.OperationId = $"Update{schemaKey}Content"; + operation.OperationId = $"Update{schemaType}Content"; operation.Summary = $"Update a {schemaName} content."; operation.Security = EditorSecurity; @@ -169,8 +170,8 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Patch, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => { - operation.OperationId = $"Path{schemaKey}Content"; - operation.Summary = $"Patchs a {schemaName} content."; + operation.OperationId = $"Path{schemaType}Content"; + operation.Summary = $"Patch a {schemaName} content."; operation.Security = EditorSecurity; operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription); @@ -183,7 +184,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/publish", operation => { - operation.OperationId = $"Publish{schemaKey}Content"; + operation.OperationId = $"Publish{schemaType}Content"; operation.Summary = $"Publish a {schemaName} content."; operation.Security = EditorSecurity; @@ -195,7 +196,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/unpublish", operation => { - operation.OperationId = $"Unpublish{schemaKey}Content"; + operation.OperationId = $"Unpublish{schemaType}Content"; operation.Summary = $"Unpublish a {schemaName} content."; operation.Security = EditorSecurity; @@ -207,7 +208,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/archive", operation => { - operation.OperationId = $"Archive{schemaKey}Content"; + operation.OperationId = $"Archive{schemaType}Content"; operation.Summary = $"Archive a {schemaName} content."; operation.Security = EditorSecurity; @@ -219,7 +220,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/restore", operation => { - operation.OperationId = $"Restore{schemaKey}Content"; + operation.OperationId = $"Restore{schemaType}Content"; operation.Summary = $"Restore a {schemaName} content."; operation.Security = EditorSecurity; @@ -231,7 +232,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { return AddOperation(SwaggerOperationMethod.Delete, schemaName, $"{appPath}/{schemaPath}/{{id}}/", operation => { - operation.OperationId = $"Delete{schemaKey}Content"; + operation.OperationId = $"Delete{schemaType}Content"; operation.Summary = $"Delete a {schemaName} content."; operation.Security = EditorSecurity; diff --git a/src/Squidex/Areas/Api/Controllers/Content/Models/AssetsDto.cs b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentsDto.cs similarity index 95% rename from src/Squidex/Areas/Api/Controllers/Content/Models/AssetsDto.cs rename to src/Squidex/Areas/Api/Controllers/Content/Models/ContentsDto.cs index 6be8cbf15..12c19cd9c 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Models/AssetsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentsDto.cs @@ -7,7 +7,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models { - public sealed class AssetsDto + public sealed class ContentsDto { /// /// The total number of content items. diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs index 28741a599..ce1b72ced 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs @@ -27,13 +27,21 @@ namespace Squidex.Pipeline.CommandMiddlewares public Task HandleAsync(CommandContext context, Func next) { - if (context.Command is SquidexCommand squidexCommand && squidexCommand.Actor == null) + if (context.Command is SquidexCommand squidexCommand) { - var actorToken = - FindActorFromSubject() ?? - FindActorFromClient(); + if (squidexCommand.Actor == null) + { + var actorToken = + FindActorFromSubject() ?? + FindActorFromClient(); - squidexCommand.Actor = actorToken ?? throw new SecurityException("No actor with subject or client id available."); + squidexCommand.Actor = actorToken ?? throw new SecurityException("No actor with subject or client id available."); + } + + if (squidexCommand.User == null) + { + squidexCommand.User = httpContextAccessor.HttpContext.User; + } } return next(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs new file mode 100644 index 000000000..509ba6db0 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -0,0 +1,207 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Xunit; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL +{ + public class GraphQLMutationTests : GraphQLTestBase + { + private readonly CommandContext commandContext = new CommandContext(new PatchContent()); + + public GraphQLMutationTests() + { + A.CallTo(() => commandBus.PublishAsync(A.Ignored)) + .Returns(commandContext); + } + + [Fact] + public async Task Should_return_single_content_when_patching_content() + { + var contentId = Guid.NewGuid(); + var content = CreateContent(contentId, Guid.Empty, Guid.Empty); + + var query = $@" + mutation OP($data: MySchemaInputDto!) {{ + patchMySchemaContent(id: ""{contentId}"", data: $data) {{ + myString {{ + de + }} + myNumber {{ + iv + }} + myBoolean {{ + iv + }} + myDatetime {{ + iv + }} + myJson {{ + iv + }} + myGeolocation {{ + iv + }} + myTags {{ + iv + }} + }} + }}"; + + commandContext.Complete(new ContentDataChangedResult(content.Data, 1)); + + var camelContent = new NamedContentData(); + + foreach (var kvp in content.Data) + { + if (kvp.Key != "my-json") + { + camelContent[kvp.Key.ToCamelCase()] = kvp.Value; + } + } + + var variables = + new JObject( + new JProperty("data", JObject.FromObject(camelContent))); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + + var expected = new + { + data = new + { + patchMySchemaContent = new + { + myString = new + { + de = "value" + }, + myNumber = new + { + iv = 1 + }, + myBoolean = new + { + iv = true + }, + myDatetime = new + { + iv = content.LastModified.ToDateTimeUtc() + }, + myJson = new + { + iv = new + { + value = 1 + } + }, + myGeolocation = new + { + iv = new + { + latitude = 10, + longitude = 20 + } + }, + myTags = new + { + iv = new[] + { + "tag1", + "tag2" + } + } + } + } + }; + + AssertResult(expected, result); + } + + [Fact] + public async Task Should_publish_command_for_restore() + { + var contentId = Guid.NewGuid(); + + var query = $@" + mutation {{ + restoreMySchemaContent(id: ""{contentId}"") {{ + version + }} + }}"; + + commandContext.Complete(new EntitySavedResult(13)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + restoreMySchemaContent = new + { + version = 13 + } + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schema.NamedId()) && + x.ContentId == contentId && + x.Status == Status.Draft))) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_publish_command_for_delete() + { + var contentId = Guid.NewGuid(); + + var query = $@" + mutation {{ + deleteMySchemaContent(id: ""{contentId}"") {{ + version + }} + }}"; + + commandContext.Complete(new EntitySavedResult(13)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + deleteMySchemaContent = new + { + version = 13 + } + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schema.NamedId()) && + x.ContentId == contentId))) + .MustHaveHappened(); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs similarity index 77% rename from tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index d5f7e6176..5b6d937a2 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -7,87 +7,17 @@ using System; using System.Collections.Generic; -using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NodaTime.Extensions; -using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; -using Squidex.Domain.Apps.Entities.Assets.Repositories; -using Squidex.Domain.Apps.Entities.Contents.TestData; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Xunit; -#pragma warning disable SA1311 // Static readonly fields must begin with upper-case letter - namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public class GraphQLTests + public class GraphQLQueriesTests : GraphQLTestBase { - private static readonly Guid schemaId = Guid.NewGuid(); - private static readonly Guid appId = Guid.NewGuid(); - private static readonly string appName = "my-app"; - private readonly Schema schemaDef; - private readonly IContentQueryService contentQuery = A.Fake(); - private readonly IAssetRepository assetRepository = A.Fake(); - private readonly ISchemaEntity schema = A.Fake(); - private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); - private readonly IAppProvider appProvider = A.Fake(); - private readonly IAppEntity app = A.Dummy(); - private readonly ClaimsPrincipal user = new ClaimsPrincipal(); - private readonly IGraphQLService sut; - - public GraphQLTests() - { - schemaDef = - new Schema("my-schema") - .AddField(new JsonField(1, "my-json", Partitioning.Invariant, - new JsonFieldProperties())) - .AddField(new StringField(2, "my-string", Partitioning.Language, - new StringFieldProperties())) - .AddField(new NumberField(3, "my-number", Partitioning.Invariant, - new NumberFieldProperties())) - .AddField(new AssetsField(4, "my-assets", Partitioning.Invariant, - new AssetsFieldProperties())) - .AddField(new BooleanField(5, "my-boolean", Partitioning.Invariant, - new BooleanFieldProperties())) - .AddField(new DateTimeField(6, "my-datetime", Partitioning.Invariant, - new DateTimeFieldProperties())) - .AddField(new ReferencesField(7, "my-references", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = schemaId })) - .AddField(new ReferencesField(9, "my-invalid", Partitioning.Invariant, - new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })) - .AddField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant, - new GeolocationFieldProperties())) - .AddField(new TagsField(11, "my-tags", Partitioning.Invariant, - new TagsFieldProperties())); - - A.CallTo(() => app.Id).Returns(appId); - A.CallTo(() => app.Name).Returns(appName); - A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE)); - - A.CallTo(() => schema.Id).Returns(schemaId); - A.CallTo(() => schema.Name).Returns(schemaDef.Name); - A.CallTo(() => schema.SchemaDef).Returns(schemaDef); - A.CallTo(() => schema.IsPublished).Returns(true); - A.CallTo(() => schema.ScriptQuery).Returns(""); - - var allSchemas = new List { schema }; - - A.CallTo(() => appProvider.GetSchemasAsync(appId)).Returns(allSchemas); - - sut = new CachingGraphQLService(cache, appProvider, assetRepository, contentQuery, new FakeUrlGenerator()); - } - [Theory] [InlineData(null)] [InlineData("")] @@ -103,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -111,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { const string query = @" query { - queryAssets(search: ""my-query"", top: 30, skip: 5) { + queryAssets(search: ""my-query"", take: 30, skip: 5) { id version created @@ -169,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -177,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { const string query = @" query { - queryAssetsWithTotal(search: ""my-query"", top: 30, skip: 5) { + queryAssetsWithTotal(search: ""my-query"", take: 30, skip: 5) { total items { id @@ -242,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -304,7 +234,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -417,7 +347,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -537,7 +467,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -646,7 +576,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -706,7 +636,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -766,7 +696,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } }; - AssertJson(expected, new { data = result.Data }); + AssertResult(expected, result); } [Fact] @@ -803,78 +733,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL data = (object)null }; - AssertJson(expected, new { data = result.Data }); - } - - private static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null) - { - var now = DateTime.UtcNow.ToInstant(); - - data = data ?? - new NamedContentData() - .AddField("my-json", - new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))) - .AddField("my-string", - new ContentFieldData().AddValue("de", "value")) - .AddField("my-assets", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { assetId }))) - .AddField("my-number", - new ContentFieldData().AddValue("iv", 1)) - .AddField("my-boolean", - new ContentFieldData().AddValue("iv", true)) - .AddField("my-datetime", - new ContentFieldData().AddValue("iv", now.ToDateTimeUtc())) - .AddField("my-tags", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { "tag1", "tag2" }))) - .AddField("my-references", - new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { refId }))) - .AddField("my-geolocation", - new ContentFieldData().AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); - - var content = new FakeContentEntity - { - Id = id, - Version = 1, - Created = now, - CreatedBy = new RefToken("subject", "user1"), - LastModified = now, - LastModifiedBy = new RefToken("subject", "user2"), - Data = data - }; - - return content; - } - - private static IAssetEntity CreateAsset(Guid id) - { - var now = DateTime.UtcNow.ToInstant(); - - var asset = new FakeAssetEntity - { - Id = id, - Version = 1, - Created = now, - CreatedBy = new RefToken("subject", "user1"), - LastModified = now, - LastModifiedBy = new RefToken("subject", "user2"), - FileName = "MyFile.png", - FileSize = 1024, - FileVersion = 123, - MimeType = "image/png", - IsImage = true, - PixelWidth = 800, - PixelHeight = 600 - }; - - return asset; - } - - private static void AssertJson(object expected, object result) - { - var resultJson = JsonConvert.SerializeObject(result, Formatting.Indented); - var expectJson = JsonConvert.SerializeObject(expected, Formatting.Indented); - - Assert.Equal(expectJson, resultJson); + AssertResult(expected, result, false); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs new file mode 100644 index 000000000..d7a74e669 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -0,0 +1,169 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Security.Claims; +using FakeItEasy; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NodaTime.Extensions; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.TestData; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; +using Xunit; + +#pragma warning disable SA1311 // Static readonly fields must begin with upper-case letter +#pragma warning disable SA1401 // Fields must be private + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL +{ + public class GraphQLTestBase + { + protected static readonly Guid schemaId = Guid.NewGuid(); + protected static readonly Guid appId = Guid.NewGuid(); + protected static readonly string appName = "my-app"; + protected readonly Schema schemaDef; + protected readonly IContentQueryService contentQuery = A.Fake(); + protected readonly ICommandBus commandBus = A.Fake(); + protected readonly IAssetRepository assetRepository = A.Fake(); + protected readonly ISchemaEntity schema = A.Fake(); + protected readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); + protected readonly IAppProvider appProvider = A.Fake(); + protected readonly IAppEntity app = A.Dummy(); + protected readonly ClaimsPrincipal user = new ClaimsPrincipal(); + protected readonly IGraphQLService sut; + + public GraphQLTestBase() + { + schemaDef = + new Schema("my-schema") + .AddField(new JsonField(1, "my-json", Partitioning.Invariant, + new JsonFieldProperties())) + .AddField(new StringField(2, "my-string", Partitioning.Language, + new StringFieldProperties())) + .AddField(new NumberField(3, "my-number", Partitioning.Invariant, + new NumberFieldProperties())) + .AddField(new AssetsField(4, "my-assets", Partitioning.Invariant, + new AssetsFieldProperties())) + .AddField(new BooleanField(5, "my-boolean", Partitioning.Invariant, + new BooleanFieldProperties())) + .AddField(new DateTimeField(6, "my-datetime", Partitioning.Invariant, + new DateTimeFieldProperties())) + .AddField(new ReferencesField(7, "my-references", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = schemaId })) + .AddField(new ReferencesField(9, "my-invalid", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = Guid.NewGuid() })) + .AddField(new GeolocationField(10, "my-geolocation", Partitioning.Invariant, + new GeolocationFieldProperties())) + .AddField(new TagsField(11, "my-tags", Partitioning.Invariant, + new TagsFieldProperties())); + + A.CallTo(() => app.Id).Returns(appId); + A.CallTo(() => app.Name).Returns(appName); + A.CallTo(() => app.LanguagesConfig).Returns(LanguagesConfig.Build(Language.DE)); + + A.CallTo(() => schema.Id).Returns(schemaId); + A.CallTo(() => schema.Name).Returns(schemaDef.Name); + A.CallTo(() => schema.SchemaDef).Returns(schemaDef); + A.CallTo(() => schema.IsPublished).Returns(true); + A.CallTo(() => schema.ScriptQuery).Returns(""); + + var allSchemas = new List { schema }; + + A.CallTo(() => appProvider.GetSchemasAsync(appId)).Returns(allSchemas); + + sut = new CachingGraphQLService(cache, appProvider, assetRepository, commandBus, contentQuery, new FakeUrlGenerator()); + } + + protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null) + { + var now = DateTime.UtcNow.ToInstant(); + + data = data ?? + new NamedContentData() + .AddField("my-json", + new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))) + .AddField("my-string", + new ContentFieldData().AddValue("de", "value")) + .AddField("my-assets", + new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { assetId }))) + .AddField("my-number", + new ContentFieldData().AddValue("iv", 1)) + .AddField("my-boolean", + new ContentFieldData().AddValue("iv", true)) + .AddField("my-datetime", + new ContentFieldData().AddValue("iv", now.ToDateTimeUtc())) + .AddField("my-tags", + new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { "tag1", "tag2" }))) + .AddField("my-references", + new ContentFieldData().AddValue("iv", JToken.FromObject(new[] { refId }))) + .AddField("my-geolocation", + new ContentFieldData().AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); + + var content = new ContentEntity + { + Id = id, + Version = 1, + Created = now, + CreatedBy = new RefToken("subject", "user1"), + LastModified = now, + LastModifiedBy = new RefToken("subject", "user2"), + Data = data + }; + + return content; + } + + protected static IAssetEntity CreateAsset(Guid id) + { + var now = DateTime.UtcNow.ToInstant(); + + var asset = new FakeAssetEntity + { + Id = id, + Version = 1, + Created = now, + CreatedBy = new RefToken("subject", "user1"), + LastModified = now, + LastModifiedBy = new RefToken("subject", "user2"), + FileName = "MyFile.png", + FileSize = 1024, + FileVersion = 123, + MimeType = "image/png", + IsImage = true, + PixelWidth = 800, + PixelHeight = 600 + }; + + return asset; + } + + protected static void AssertResult(object expected, (object Data, object[] Errors) result, bool checkErrors = true) + { + if (checkErrors && (result.Errors != null && result.Errors.Length > 0)) + { + throw new InvalidOperationException(result.Errors[0]?.ToString()); + } + + var resultJson = JsonConvert.SerializeObject(new { data = result.Data }, Formatting.Indented); + var expectJson = JsonConvert.SerializeObject(expected, Formatting.Indented); + + Assert.Equal(expectJson, resultJson); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs deleted file mode 100644 index c7f0d9af8..000000000 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using NodaTime; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Entities.Contents.TestData -{ - public sealed class FakeContentEntity : IContentEntity - { - public Guid Id { get; set; } - - public Guid AppId { get; set; } - - public long Version { get; set; } - - public Instant Created { get; set; } - - public Instant LastModified { get; set; } - - public RefToken CreatedBy { get; set; } - - public RefToken LastModifiedBy { get; set; } - - public NamedContentData Data { get; set; } - - public Status Status { get; set; } - } -} From 7f014a9c5de5c581bc34c843e6b24586f4384bda Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 01:27:36 +0100 Subject: [PATCH 2/7] Finish change status endpoints. --- .../GraphQL/Types/CommandVersionGraphType.cs | 6 +- .../Contents/GraphQL/GraphQLMutationTests.cs | 111 ++++++++++++++++++ 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs index 30fb044ea..dd29901d8 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure.Commands; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed class CommandVersionGraphType : ComplexGraphType + public sealed class CommandVersionGraphType : ObjectGraphType { public CommandVersionGraphType() { @@ -21,14 +21,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "version", ResolvedType = new IntGraphType(), - Resolver = ResolveEtag(), + Resolver = ResolveVersion(), Description = "The new version of the item." }); Description = "The result of a mutation"; } - private static IFieldResolver ResolveEtag() + private static IFieldResolver ResolveVersion() { return new FuncFieldResolver(x => { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index 509ba6db0..9c513477e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -131,6 +131,117 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL AssertResult(expected, result); } + [Fact] + public async Task Should_publish_command_for_publish() + { + var contentId = Guid.NewGuid(); + + var query = $@" + mutation {{ + publishMySchemaContent(id: ""{contentId}"") {{ + version + }} + }}"; + + commandContext.Complete(new EntitySavedResult(13)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + publishMySchemaContent = new + { + version = 13 + } + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schema.NamedId()) && + x.ContentId == contentId && + x.Status == Status.Published))) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_publish_command_for_unpublish() + { + var contentId = Guid.NewGuid(); + + var query = $@" + mutation {{ + unpublishMySchemaContent(id: ""{contentId}"") {{ + version + }} + }}"; + + commandContext.Complete(new EntitySavedResult(13)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + unpublishMySchemaContent = new + { + version = 13 + } + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schema.NamedId()) && + x.ContentId == contentId && + x.Status == Status.Draft))) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_publish_command_for_archive() + { + var contentId = Guid.NewGuid(); + + var query = $@" + mutation {{ + archiveMySchemaContent(id: ""{contentId}"") {{ + version + }} + }}"; + + commandContext.Complete(new EntitySavedResult(13)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); + + var expected = new + { + data = new + { + archiveMySchemaContent = new + { + version = 13 + } + } + }; + + AssertResult(expected, result); + + A.CallTo(() => commandBus.PublishAsync( + A.That.Matches(x => + x.SchemaId.Equals(schema.NamedId()) && + x.ContentId == contentId && + x.Status == Status.Archived))) + .MustHaveHappened(); + } + [Fact] public async Task Should_publish_command_for_restore() { From 343f76aa611ccf72a9301a70a945f0049347105a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 01:42:01 +0100 Subject: [PATCH 3/7] Update operation finished. --- .../GraphQL/Types/AppMutationsGraphType.cs | 20 +- .../ContentDataChangedResultGraphType.cs | 44 +++ .../Contents/GraphQL/GraphQLMutationTests.cs | 266 +++++++++++++----- 3 files changed, 248 insertions(+), 82 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs 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 dc3ac8a4e..e3c3a22d8 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -32,11 +32,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var contentType = model.GetContentType(schema.Id); var contentDataType = model.GetContentDataType(schema.Id); + var resultType = new ContentDataChangedResultGraphType(schemaType, schemaName, contentDataType); + var inputType = new ContentDataGraphInputType(model, schema); AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType); - AddContentUpdate(schemaId, schemaType, schemaName, inputType, contentDataType); - AddContentPatch(schemaId, schemaType, schemaName, inputType, contentDataType); + AddContentUpdate(schemaId, schemaType, schemaName, inputType, resultType); + AddContentPatch(schemaId, schemaType, schemaName, inputType, resultType); AddContentPublish(schemaId, schemaType, schemaName); AddContentUnpublish(schemaId, schemaType, schemaName); AddContentArchive(schemaId, schemaType, schemaName); @@ -93,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types }); } - private void AddContentUpdate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType) + private void AddContentUpdate(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) { AddField(new FieldType { @@ -120,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types DefaultValue = EtagVersion.Any } }, - ResolvedType = new NonNullGraphType(contentDataType), + ResolvedType = new NonNullGraphType(resultType), Resolver = ResolveAsync(async (c, publish) => { var contentId = c.GetArgument("id"); @@ -131,13 +133,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var result = commandContext.Result(); - return result.Data; + return result; }), Description = $"Update an {schemaName} content by id." }); } - private void AddContentPatch(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType contentDataType) + private void AddContentPatch(NamedId schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IComplexGraphType resultType) { AddField(new FieldType { @@ -164,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types DefaultValue = EtagVersion.Any } }, - ResolvedType = new NonNullGraphType(contentDataType), + ResolvedType = new NonNullGraphType(resultType), Resolver = ResolveAsync(async (c, publish) => { var contentId = c.GetArgument("id"); @@ -175,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var result = commandContext.Result(); - return result.Data; + return result; }), Description = $"Patch a {schemaName} content." }); @@ -303,7 +305,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types return action(c, command => { - command.ExpectedVersion = c.GetArgument("expectedVersion"); + command.ExpectedVersion = c.GetArgument("expectedVersion", EtagVersion.Any); return e.CommandBus.PublishAsync(command); }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs new file mode 100644 index 000000000..1c9e6e822 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using GraphQL.Resolvers; +using GraphQL.Types; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public sealed class ContentDataChangedResultGraphType : ObjectGraphType + { + public ContentDataChangedResultGraphType(string schemaType, string schemaName, IComplexGraphType contentDataType) + { + Name = $"{schemaName}DataChangedResultDto"; + + AddField(new FieldType + { + Name = "version", + ResolvedType = new IntGraphType(), + Resolver = Resolve(x => x.Version), + Description = $"The new version of the {schemaName} content." + }); + + AddField(new FieldType + { + Name = "data", + ResolvedType = new NonNullGraphType(contentDataType), + Resolver = Resolve(x => x.Data), + Description = $"The new data of the {schemaName} content." + }); + + Description = $"The result of the {schemaName} mutation"; + } + + private static IFieldResolver Resolve(Func action) + { + return new FuncFieldResolver(c => action(c.Source)); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index 9c513477e..64224e695 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -29,53 +29,150 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } [Fact] - public async Task Should_return_single_content_when_patching_content() + public async Task Should_return_single_content_when_updating_content() { var contentId = Guid.NewGuid(); var content = CreateContent(contentId, Guid.Empty, Guid.Empty); var query = $@" mutation OP($data: MySchemaInputDto!) {{ - patchMySchemaContent(id: ""{contentId}"", data: $data) {{ - myString {{ - de - }} - myNumber {{ - iv - }} - myBoolean {{ - iv - }} - myDatetime {{ - iv - }} - myJson {{ - iv - }} - myGeolocation {{ - iv - }} - myTags {{ - iv + updateMySchemaContent(id: ""{contentId}"", data: $data, expectedVersion: 10) {{ + version + data {{ + myString {{ + de + }} + myNumber {{ + iv + }} + myBoolean {{ + iv + }} + myDatetime {{ + iv + }} + myJson {{ + iv + }} + myGeolocation {{ + iv + }} + myTags {{ + iv + }} }} }} }}"; - commandContext.Complete(new ContentDataChangedResult(content.Data, 1)); + commandContext.Complete(new ContentDataChangedResult(content.Data, 13)); - var camelContent = new NamedContentData(); + var inputContent = GetInputContent(content); - foreach (var kvp in content.Data) + var variables = + new JObject( + new JProperty("data", inputContent)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + + var expected = new { - if (kvp.Key != "my-json") + data = new { - camelContent[kvp.Key.ToCamelCase()] = kvp.Value; + updateMySchemaContent = new + { + version = 13, + data = new + { + myString = new + { + de = "value" + }, + myNumber = new + { + iv = 1 + }, + myBoolean = new + { + iv = true + }, + myDatetime = new + { + iv = content.LastModified.ToDateTimeUtc() + }, + myJson = new + { + iv = new + { + value = 1 + } + }, + myGeolocation = new + { + iv = new + { + latitude = 10, + longitude = 20 + } + }, + myTags = new + { + iv = new[] + { + "tag1", + "tag2" + } + } + } + } } - } + }; + + AssertResult(expected, result); + } + + [Fact] + public async Task Should_return_single_content_when_patching_content() + { + var contentId = Guid.NewGuid(); + var content = CreateContent(contentId, Guid.Empty, Guid.Empty); + + var query = $@" + mutation OP($data: MySchemaInputDto!) {{ + patchMySchemaContent(id: ""{contentId}"", data: $data, expectedVersion: 10) {{ + version + data {{ + myString {{ + de + }} + myNumber {{ + iv + }} + myBoolean {{ + iv + }} + myDatetime {{ + iv + }} + myJson {{ + iv + }} + myGeolocation {{ + iv + }} + myTags {{ + iv + }} + }} + }} + }}"; + + commandContext.Complete(new ContentDataChangedResult(content.Data, 13)); + + var inputContent = GetInputContent(content); var variables = new JObject( - new JProperty("data", JObject.FromObject(camelContent))); + new JProperty("data", inputContent)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); @@ -85,43 +182,46 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { patchMySchemaContent = new { - myString = new - { - de = "value" - }, - myNumber = new - { - iv = 1 - }, - myBoolean = new - { - iv = true - }, - myDatetime = new - { - iv = content.LastModified.ToDateTimeUtc() - }, - myJson = new - { - iv = new + version = 13, + data = new { + myString = new { - value = 1 - } - }, - myGeolocation = new - { - iv = new + de = "value" + }, + myNumber = new { - latitude = 10, - longitude = 20 - } - }, - myTags = new - { - iv = new[] + iv = 1 + }, + myBoolean = new + { + iv = true + }, + myDatetime = new + { + iv = content.LastModified.ToDateTimeUtc() + }, + myJson = new + { + iv = new + { + value = 1 + } + }, + myGeolocation = new + { + iv = new + { + latitude = 10, + longitude = 20 + } + }, + myTags = new { - "tag1", - "tag2" + iv = new[] + { + "tag1", + "tag2" + } } } } @@ -138,7 +238,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var query = $@" mutation {{ - publishMySchemaContent(id: ""{contentId}"") {{ + publishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ version }} }}"; @@ -164,7 +264,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && - x.Status == Status.Published))) + x.Status == Status.Published && + x.ExpectedVersion == 10))) .MustHaveHappened(); } @@ -175,7 +276,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var query = $@" mutation {{ - unpublishMySchemaContent(id: ""{contentId}"") {{ + unpublishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ version }} }}"; @@ -201,7 +302,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && - x.Status == Status.Draft))) + x.Status == Status.Draft && + x.ExpectedVersion == 10))) .MustHaveHappened(); } @@ -212,7 +314,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var query = $@" mutation {{ - archiveMySchemaContent(id: ""{contentId}"") {{ + archiveMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ version }} }}"; @@ -238,7 +340,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && - x.Status == Status.Archived))) + x.Status == Status.Archived && + x.ExpectedVersion == 10))) .MustHaveHappened(); } @@ -249,7 +352,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var query = $@" mutation {{ - restoreMySchemaContent(id: ""{contentId}"") {{ + restoreMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ version }} }}"; @@ -275,7 +378,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.That.Matches(x => x.SchemaId.Equals(schema.NamedId()) && x.ContentId == contentId && - x.Status == Status.Draft))) + x.Status == Status.Draft && + x.ExpectedVersion == 10))) .MustHaveHappened(); } @@ -286,7 +390,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var query = $@" mutation {{ - deleteMySchemaContent(id: ""{contentId}"") {{ + deleteMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ version }} }}"; @@ -311,8 +415,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL A.CallTo(() => commandBus.PublishAsync( A.That.Matches(x => x.SchemaId.Equals(schema.NamedId()) && - x.ContentId == contentId))) + x.ContentId == contentId && + x.ExpectedVersion == 10))) .MustHaveHappened(); } + + private static JObject GetInputContent(IContentEntity content) + { + var camelContent = new NamedContentData(); + + foreach (var kvp in content.Data) + { + if (kvp.Key != "my-json") + { + camelContent[kvp.Key.ToCamelCase()] = kvp.Value; + } + } + + return JObject.FromObject(camelContent); + } } } From 84f56b2999f5ba0b4b952514cce970930114e878 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 01:50:26 +0100 Subject: [PATCH 4/7] Create endpoint. --- .../GraphQL/Types/AppMutationsGraphType.cs | 2 +- .../Contents/GraphQL/GraphQLMutationTests.cs | 135 ++++++++++++------ .../Contents/GraphQL/GraphQLTestBase.cs | 10 +- 3 files changed, 103 insertions(+), 44 deletions(-) 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 e3c3a22d8..a4a9e8d7d 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types var result = commandContext.Result>(); var response = ContentEntity.Create(command, result); - return ContentEntity.Create(command, result); + return (IContentEntity)ContentEntity.Create(command, result); }), Description = $"Creates an {schemaName} content." }); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs index 64224e695..ffa2afe06 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs @@ -20,23 +20,24 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public class GraphQLMutationTests : GraphQLTestBase { + private readonly Guid contentId = Guid.NewGuid(); + private readonly IContentEntity content; private readonly CommandContext commandContext = new CommandContext(new PatchContent()); public GraphQLMutationTests() { + content = CreateContent(contentId, Guid.Empty, Guid.Empty, null, true); + A.CallTo(() => commandBus.PublishAsync(A.Ignored)) .Returns(commandContext); } [Fact] - public async Task Should_return_single_content_when_updating_content() + public async Task Should_return_single_content_when_creating_content() { - var contentId = Guid.NewGuid(); - var content = CreateContent(contentId, Guid.Empty, Guid.Empty); - var query = $@" mutation OP($data: MySchemaInputDto!) {{ - updateMySchemaContent(id: ""{contentId}"", data: $data, expectedVersion: 10) {{ + createMySchemaContent(data: $data, expectedVersion: 10) {{ version data {{ myString {{ @@ -51,9 +52,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL myDatetime {{ iv }} - myJson {{ - iv - }} myGeolocation {{ iv }} @@ -64,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }} }}"; - commandContext.Complete(new ContentDataChangedResult(content.Data, 13)); + commandContext.Complete(new EntityCreatedResult(content.Data, 13)); var inputContent = GetInputContent(content); @@ -78,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { data = new { - updateMySchemaContent = new + createMySchemaContent = new { version = 13, data = new @@ -99,13 +97,95 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { iv = content.LastModified.ToDateTimeUtc() }, - myJson = new + myGeolocation = new { iv = new { - value = 1 + latitude = 10, + longitude = 20 } }, + myTags = new + { + iv = new[] + { + "tag1", + "tag2" + } + } + } + } + } + }; + + AssertResult(expected, result); + } + + [Fact] + public async Task Should_return_single_content_when_updating_content() + { + var query = $@" + mutation OP($data: MySchemaInputDto!) {{ + updateMySchemaContent(id: ""{contentId}"", data: $data, expectedVersion: 10) {{ + version + data {{ + myString {{ + de + }} + myNumber {{ + iv + }} + myBoolean {{ + iv + }} + myDatetime {{ + iv + }} + myGeolocation {{ + iv + }} + myTags {{ + iv + }} + }} + }} + }}"; + + commandContext.Complete(new ContentDataChangedResult(content.Data, 13)); + + var inputContent = GetInputContent(content); + + var variables = + new JObject( + new JProperty("data", inputContent)); + + var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query, Variables = variables }); + + var expected = new + { + data = new + { + updateMySchemaContent = new + { + version = 13, + data = new + { + myString = new + { + de = "value" + }, + myNumber = new + { + iv = 1 + }, + myBoolean = new + { + iv = true + }, + myDatetime = new + { + iv = content.LastModified.ToDateTimeUtc() + }, myGeolocation = new { iv = new @@ -133,9 +213,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_return_single_content_when_patching_content() { - var contentId = Guid.NewGuid(); - var content = CreateContent(contentId, Guid.Empty, Guid.Empty); - var query = $@" mutation OP($data: MySchemaInputDto!) {{ patchMySchemaContent(id: ""{contentId}"", data: $data, expectedVersion: 10) {{ @@ -153,9 +230,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL myDatetime {{ iv }} - myJson {{ - iv - }} myGeolocation {{ iv }} @@ -183,7 +257,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL patchMySchemaContent = new { version = 13, - data = new { + data = new + { myString = new { de = "value" @@ -200,13 +275,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { iv = content.LastModified.ToDateTimeUtc() }, - myJson = new - { - iv = new - { - value = 1 - } - }, myGeolocation = new { iv = new @@ -234,8 +302,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_publish_command_for_publish() { - var contentId = Guid.NewGuid(); - var query = $@" mutation {{ publishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ @@ -272,8 +338,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_publish_command_for_unpublish() { - var contentId = Guid.NewGuid(); - var query = $@" mutation {{ unpublishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ @@ -310,8 +374,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_publish_command_for_archive() { - var contentId = Guid.NewGuid(); - var query = $@" mutation {{ archiveMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ @@ -348,8 +410,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_publish_command_for_restore() { - var contentId = Guid.NewGuid(); - var query = $@" mutation {{ restoreMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ @@ -386,8 +446,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL [Fact] public async Task Should_publish_command_for_delete() { - var contentId = Guid.NewGuid(); - var query = $@" mutation {{ deleteMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{ @@ -426,10 +484,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL foreach (var kvp in content.Data) { - if (kvp.Key != "my-json") - { - camelContent[kvp.Key.ToCamelCase()] = kvp.Value; - } + camelContent[kvp.Key.ToCamelCase()] = kvp.Value; } return JObject.FromObject(camelContent); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index d7a74e669..96cfbb42c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -90,14 +90,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL sut = new CachingGraphQLService(cache, appProvider, assetRepository, commandBus, contentQuery, new FakeUrlGenerator()); } - protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null) + protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, bool noJson = false) { var now = DateTime.UtcNow.ToInstant(); data = data ?? new NamedContentData() - .AddField("my-json", - new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))) .AddField("my-string", new ContentFieldData().AddValue("de", "value")) .AddField("my-assets", @@ -115,6 +113,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .AddField("my-geolocation", new ContentFieldData().AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))); + if (!noJson) + { + data.AddField("my-json", + new ContentFieldData().AddValue("iv", JToken.FromObject(new { value = 1 }))); + } + var content = new ContentEntity { Id = id, From bc130340cb6b837dd7f2fd2fa03b6ac54ad39b72 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 11:17:05 +0100 Subject: [PATCH 5/7] Minor optimizations. --- .../Contents/GraphQL/GraphQLModel.cs | 34 +++++----- .../Contents/GraphQL/Types/AllTypes.cs | 67 +++++++++++++++++++ .../GraphQL/Types/AppMutationsGraphType.cs | 64 ++++++++++-------- .../GraphQL/Types/AppQueriesGraphType.cs | 50 ++++++++------ .../Contents/GraphQL/Types/AssetGraphType.cs | 34 +++++----- .../GraphQL/Types/AssetsResultGraphType.cs | 2 +- .../GraphQL/Types/CommandVersionGraphType.cs | 2 +- .../ContentDataChangedResultGraphType.cs | 2 +- .../Types/ContentDataGraphInputType.cs | 3 +- .../GraphQL/Types/ContentDataGraphType.cs | 3 +- .../GraphQL/Types/ContentGraphType.cs | 16 ++--- .../GraphQL/Types/ContentsResultGraphType.cs | 2 +- .../Types/GeolocationInputGraphType.cs | 4 +- 13 files changed, 185 insertions(+), 98 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 0c8082a31..03ceeec54 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -54,35 +54,35 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { { typeof(StringField), - new StringGraphType() + AllTypes.String }, { typeof(BooleanField), - new BooleanGraphType() + AllTypes.Boolean }, { typeof(NumberField), - new FloatGraphType() + AllTypes.Boolean }, { typeof(DateTimeField), - new DateGraphType() + AllTypes.Date }, { typeof(GeolocationField), - new GeolocationInputGraphType() + AllTypes.GeolocationInput }, { typeof(TagsField), - new ListGraphType(new StringGraphType()) + AllTypes.ListOfNonNullString }, { typeof(AssetsField), - new ListGraphType(new GuidGraphType()) + AllTypes.ListOfNonNullGuid }, { typeof(ReferencesField), - new ListGraphType(new GuidGraphType()) + AllTypes.ListOfNonNullGuid } }; @@ -90,31 +90,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { { typeof(StringField), - field => ResolveDefault("String") + field => ResolveDefault(AllTypes.NoopString) }, { typeof(BooleanField), - field => ResolveDefault("Boolean") + field => ResolveDefault(AllTypes.NoopBoolean) }, { typeof(NumberField), - field => ResolveDefault("Float") + field => ResolveDefault(AllTypes.NoopFloat) }, { typeof(DateTimeField), - field => ResolveDefault("Date") + field => ResolveDefault(AllTypes.NoopDate) }, { typeof(JsonField), - field => ResolveDefault("Json") + field => ResolveDefault(AllTypes.NoopJson) }, { typeof(GeolocationField), - field => ResolveDefault("Geolocation") + field => ResolveDefault(AllTypes.NoopGeolocation) }, { typeof(TagsField), - field => ResolveDefault("String") + field => ResolveDefault(AllTypes.NoopTags) }, { typeof(AssetsField), @@ -144,9 +144,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL } } - private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(string name) + private static (IGraphType ResolveType, IFieldResolver Resolver) ResolveDefault(IGraphType type) { - return (new NoopGraphType(name), new FuncFieldResolver(c => c.Source.GetOrDefault(c.FieldName))); + return (type, new FuncFieldResolver(c => c.Source.GetOrDefault(c.FieldName))); } public IFieldResolver ResolveAssetUrl() diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs new file mode 100644 index 000000000..dac8e7e07 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using GraphQL.Types; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types +{ + public static class AllTypes + { + public static readonly Type None = typeof(NoopGraphType); + + public static readonly IGraphType Int = new IntGraphType(); + + public static readonly IGraphType Guid = new GuidGraphType(); + + public static readonly IGraphType Date = new DateGraphType(); + + public static readonly IGraphType Float = new FloatGraphType(); + + public static readonly IGraphType String = new StringGraphType(); + + public static readonly IGraphType Boolean = new BooleanGraphType(); + + public static readonly IGraphType NonNullInt = new NonNullGraphType(new IntGraphType()); + + public static readonly IGraphType NonNullGuid = new NonNullGraphType(new GuidGraphType()); + + public static readonly IGraphType NonNullDate = new NonNullGraphType(new DateGraphType()); + + public static readonly IGraphType NonNullFloat = new NonNullGraphType(new FloatGraphType()); + + public static readonly IGraphType NonNullString = new NonNullGraphType(new StringGraphType()); + + public static readonly IGraphType NonNullBoolean = new NonNullGraphType(new BooleanGraphType()); + + public static readonly IGraphType ListOfNonNullGuid = new ListGraphType(new NonNullGraphType(new GuidGraphType())); + + public static readonly IGraphType ListOfNonNullString = new ListGraphType(new NonNullGraphType(new StringGraphType())); + + public static readonly IGraphType NoopInt = new NoopGraphType("Int"); + + public static readonly IGraphType NoopGuid = new NoopGraphType("Guid"); + + public static readonly IGraphType NoopDate = new NoopGraphType("Date"); + + public static readonly IGraphType NoopJson = new NoopGraphType("Json"); + + public static readonly IGraphType NoopTags = new NoopGraphType("Tags"); + + 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 NoopGeolocation = new NoopGraphType("Geolocation"); + + public static readonly IGraphType CommandVersion = new CommandVersionGraphType(); + + public static readonly IGraphType GeolocationInput = new GeolocationInputGraphType(); + } +} 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 a4a9e8d7d..93275e729 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -56,24 +56,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"create{schemaType}Content", Arguments = new QueryArguments { - new QueryArgument(typeof(BooleanGraphType)) - { - Name = "publish", - Description = "Set to true to autopublish content.", - DefaultValue = false - }, - new QueryArgument(typeof(NoopGraphType)) + new QueryArgument(AllTypes.None) { Name = "data", Description = $"The data for the {schemaName} content.", DefaultValue = null, ResolvedType = new NonNullGraphType(inputType), }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) + { + Name = "publish", + Description = "Set to true to autopublish content.", + DefaultValue = false, + ResolvedType = AllTypes.Boolean + }, + new QueryArgument(AllTypes.None) { Name = "expectedVersion", Description = "The expected version", - DefaultValue = EtagVersion.Any + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int } }, ResolvedType = new NonNullGraphType(contentType), @@ -102,24 +104,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"update{schemaType}Content", Arguments = new QueryArguments { - new QueryArgument(typeof(NonNullGraphType)) + new QueryArgument(AllTypes.None) { Name = "id", Description = $"The id of the {schemaName} content (GUID)", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullGuid }, - new QueryArgument(typeof(NoopGraphType)) + new QueryArgument(AllTypes.None) { Name = "data", Description = $"The data for the {schemaName} content.", DefaultValue = null, ResolvedType = new NonNullGraphType(inputType), }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "expectedVersion", Description = "The expected version", - DefaultValue = EtagVersion.Any + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int } }, ResolvedType = new NonNullGraphType(resultType), @@ -146,24 +150,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types Name = $"patch{schemaType}Content", Arguments = new QueryArguments { - new QueryArgument(typeof(NonNullGraphType)) + new QueryArgument(AllTypes.None) { Name = "id", Description = $"The id of the {schemaName} content (GUID)", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullGuid }, - new QueryArgument(typeof(NoopGraphType)) + new QueryArgument(AllTypes.None) { Name = "data", Description = $"The data for the {schemaName} content.", DefaultValue = null, ResolvedType = new NonNullGraphType(inputType), }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "expectedVersion", Description = "The expected version", - DefaultValue = EtagVersion.Any + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int } }, ResolvedType = new NonNullGraphType(resultType), @@ -189,7 +195,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = $"publish{schemaType}Content", Arguments = CreateIdArguments(schemaName), - ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + ResolvedType = AllTypes.CommandVersion, Resolver = ResolveAsync((c, publish) => { var contentId = c.GetArgument("id"); @@ -208,7 +214,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = $"unpublish{schemaType}Content", Arguments = CreateIdArguments(schemaName), - ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + ResolvedType = AllTypes.CommandVersion, Resolver = ResolveAsync((c, publish) => { var contentId = c.GetArgument("id"); @@ -227,7 +233,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = $"archive{schemaType}Content", Arguments = CreateIdArguments(schemaName), - ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + ResolvedType = AllTypes.CommandVersion, Resolver = ResolveAsync((c, publish) => { var contentId = c.GetArgument("id"); @@ -246,7 +252,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = $"restore{schemaType}Content", Arguments = CreateIdArguments(schemaName), - ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + ResolvedType = AllTypes.CommandVersion, Resolver = ResolveAsync((c, publish) => { var contentId = c.GetArgument("id"); @@ -265,7 +271,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = $"delete{schemaType}Content", Arguments = CreateIdArguments(schemaName), - ResolvedType = new NonNullGraphType(new CommandVersionGraphType()), + ResolvedType = AllTypes.CommandVersion, Resolver = ResolveAsync((c, publish) => { var contentId = c.GetArgument("id"); @@ -282,17 +288,19 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(GuidGraphType)) + new QueryArgument(AllTypes.None) { Name = "id", Description = $"The id of the {schemaName} content (GUID)", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullGuid }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "expectedVersion", Description = "The expected version", - DefaultValue = EtagVersion.Any + DefaultValue = EtagVersion.Any, + ResolvedType = AllTypes.Int } }; } 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 06734c014..40c64fdbf 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppQueriesGraphType.cs @@ -143,11 +143,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(NonNullGraphType)) + new QueryArgument(AllTypes.None) { Name = "id", Description = "The id of the asset (GUID).", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullGuid } }; } @@ -156,11 +157,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(NonNullGraphType)) + new QueryArgument(AllTypes.None) { Name = "id", Description = $"The id of the {schemaName} content (GUID)", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.NonNullGuid } }; } @@ -169,23 +171,26 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "take", Description = "Optional number of assets to take (Default: 20).", - DefaultValue = 20 + DefaultValue = 20, + ResolvedType = AllTypes.Int }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "skip", Description = "Optional number of assets to skip.", - DefaultValue = 0 + DefaultValue = 0, + ResolvedType = AllTypes.Int }, - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(AllTypes.None) { Name = "search", Description = "Optional query to limit the files by name.", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.String } }; } @@ -194,35 +199,40 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { return new QueryArguments { - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "top", Description = "Optional number of contents to take (Default: 20).", - DefaultValue = 20 + DefaultValue = 20, + ResolvedType = AllTypes.Int }, - new QueryArgument(typeof(IntGraphType)) + new QueryArgument(AllTypes.None) { Name = "skip", Description = "Optional number of contents to skip.", - DefaultValue = 0 + DefaultValue = 0, + ResolvedType = AllTypes.Int }, - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(AllTypes.None) { Name = "filter", Description = "Optional OData filter.", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.String }, - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(AllTypes.None) { Name = "search", Description = "Optional OData full text search.", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.String }, - new QueryArgument(typeof(StringGraphType)) + new QueryArgument(AllTypes.None) { Name = "orderby", Description = "Optional OData order definition.", - DefaultValue = string.Empty + DefaultValue = string.Empty, + ResolvedType = AllTypes.String } }; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 9c2ade9ab..47e32ed04 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "id", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullGuid, Resolver = Resolve(x => x.Id.ToString()), Description = "The id of the asset." }); @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "version", - ResolvedType = new NonNullGraphType(new IntGraphType()), + ResolvedType = AllTypes.NonNullInt, Resolver = Resolve(x => x.Version), Description = "The version of the asset." }); @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "created", - ResolvedType = new NonNullGraphType(new DateGraphType()), + ResolvedType = AllTypes.NonNullDate, Resolver = Resolve(x => x.Created.ToDateTimeUtc()), Description = "The date and time when the asset has been created." }); @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "createdBy", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.CreatedBy.ToString()), Description = "The user that has created the asset." }); @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "lastModified", - ResolvedType = new NonNullGraphType(new DateGraphType()), + ResolvedType = AllTypes.NonNullDate, Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), Description = "The date and time when the asset has been modified last." }); @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "lastModifiedBy", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.LastModifiedBy.ToString()), Description = "The user that has updated the asset last." }); @@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "mimeType", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.MimeType), Description = "The mime type." }); @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "url", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = model.ResolveAssetUrl(), Description = "The url to the asset." }); @@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "thumbnailUrl", - ResolvedType = new StringGraphType(), + ResolvedType = AllTypes.String, Resolver = model.ResolveAssetThumbnailUrl(), Description = "The thumbnail url to the asset." }); @@ -94,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "fileName", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.FileName), Description = "The file name." }); @@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "fileType", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.FileName.FileType()), Description = "The file type." }); @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "fileSize", - ResolvedType = new NonNullGraphType(new IntGraphType()), + ResolvedType = AllTypes.NonNullInt, Resolver = Resolve(x => x.FileSize), Description = "The size of the file in bytes." }); @@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "fileVersion", - ResolvedType = new NonNullGraphType(new IntGraphType()), + ResolvedType = AllTypes.NonNullInt, Resolver = Resolve(x => x.FileVersion), Description = "The version of the file." }); @@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "isImage", - ResolvedType = new NonNullGraphType(new BooleanGraphType()), + ResolvedType = AllTypes.NonNullBoolean, Resolver = Resolve(x => x.IsImage), Description = "Determines of the created file is an image." }); @@ -134,7 +134,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "pixelWidth", - ResolvedType = new IntGraphType(), + ResolvedType = AllTypes.Int, Resolver = Resolve(x => x.PixelWidth), Description = "The width of the image in pixels if the asset is an image." }); @@ -142,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "pixelHeight", - ResolvedType = new IntGraphType(), + ResolvedType = AllTypes.Int, Resolver = Resolve(x => x.PixelHeight), Description = "The height of the image in pixels if the asset is an image." }); @@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "sourceUrl", - ResolvedType = new StringGraphType(), + ResolvedType = AllTypes.NonNullString, Resolver = model.ResolveAssetSourceUrl(), Description = "The source url of the asset." }); 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 1114e70a3..b86685071 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetsResultGraphType.cs @@ -22,8 +22,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "total", + ResolvedType = AllTypes.Int, Resolver = Resolve(x => x.Total), - ResolvedType = new NonNullGraphType(new IntGraphType()), Description = $"The total count of assets." }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs index dd29901d8..9fdc792f2 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "version", - ResolvedType = new IntGraphType(), + ResolvedType = AllTypes.Int, Resolver = ResolveVersion(), Description = "The new version of the item." }); 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 1c9e6e822..ebe00aadb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "version", - ResolvedType = new IntGraphType(), + ResolvedType = AllTypes.Int, Resolver = Resolve(x => x.Version), Description = $"The new version of the {schemaName} content." }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs index 440ea753a..42512c861 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs @@ -62,7 +62,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = field.Name.ToCamelCase(), Resolver = fieldResolver, - ResolvedType = fieldGraphType + ResolvedType = fieldGraphType, + Description = $"The {fieldName} field." }); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index b9822826b..d74c6e383 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -57,7 +57,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = field.Name.ToCamelCase(), Resolver = fieldResolver, - ResolvedType = fieldGraphType + ResolvedType = fieldGraphType, + Description = $"The {fieldName} field." }); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index b8dc4b304..c23eaeaea 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -25,15 +25,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "id", - ResolvedType = new NonNullGraphType(new StringGraphType()), - Resolver = Resolve(x => x.Id.ToString()), + ResolvedType = AllTypes.NonNullGuid, + Resolver = Resolve(x => x.Id), Description = $"The id of the {schemaName} content." }); AddField(new FieldType { Name = "version", - ResolvedType = new NonNullGraphType(new IntGraphType()), + ResolvedType = AllTypes.NonNullInt, Resolver = Resolve(x => x.Version), Description = $"The version of the {schemaName} content." }); @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "created", - ResolvedType = new NonNullGraphType(new DateGraphType()), + ResolvedType = AllTypes.NonNullDate, Resolver = Resolve(x => x.Created.ToDateTimeUtc()), Description = $"The date and time when the {schemaName} content has been created." }); @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "createdBy", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.CreatedBy.ToString()), Description = $"The user that has created the {schemaName} content." }); @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "lastModified", - ResolvedType = new NonNullGraphType(new DateGraphType()), + ResolvedType = AllTypes.NonNullDate, Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), Description = $"The date and time when the {schemaName} content has been modified last." }); @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "lastModifiedBy", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = Resolve(x => x.LastModifiedBy.ToString()), Description = $"The user that has updated the {schemaName} content last." }); @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "url", - ResolvedType = new NonNullGraphType(new StringGraphType()), + ResolvedType = AllTypes.NonNullString, Resolver = model.ResolveContentUrl(schema), Description = $"The url to the the {schemaName} content." }); 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 c8992e252..c3c052d06 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentsResultGraphType.cs @@ -22,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "total", Resolver = Resolver(x => x.Total), - ResolvedType = new NonNullGraphType(new IntGraphType()), + ResolvedType = AllTypes.NonNullInt, Description = $"The total number of {schemaName} items." }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs index 6de8c1405..73ba49b5c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs @@ -18,13 +18,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types AddField(new FieldType { Name = "latitude", - ResolvedType = new NonNullGraphType(new FloatGraphType()) + ResolvedType = AllTypes.NonNullFloat }); AddField(new FieldType { Name = "longitude", - ResolvedType = new NonNullGraphType(new FloatGraphType()) + ResolvedType = AllTypes.NonNullFloat }); } } From 9a7e01f7c4d6df2f9699444f4e5a04ee7b5d8898 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 17 Jan 2018 12:21:15 +0100 Subject: [PATCH 6/7] Error handling improved. --- .../GraphQL/Types/AppMutationsGraphType.cs | 26 +++++++++++++++---- src/Squidex/Squidex.csproj | 1 + src/Squidex/WebStartup.cs | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) 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 93275e729..22c30512c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; using Newtonsoft.Json.Linq; @@ -307,16 +308,31 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types private static IFieldResolver ResolveAsync(Func>, Task> action) { - return new FuncFieldResolver>(c => + return new FuncFieldResolver>(async c => { var e = (GraphQLExecutionContext)c.UserContext; - return action(c, command => + try { - command.ExpectedVersion = c.GetArgument("expectedVersion", EtagVersion.Any); + return await action(c, command => + { + command.ExpectedVersion = c.GetArgument("expectedVersion", EtagVersion.Any); + + return e.CommandBus.PublishAsync(command); + }); + } + catch (ValidationException ex) + { + c.Errors.Add(new ExecutionError(ex.Message)); - return e.CommandBus.PublishAsync(command); - }); + throw; + } + catch (DomainException ex) + { + c.Errors.Add(new ExecutionError(ex.Message)); + + throw; + } }); } diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 0f1cfd7b0..ce5079f50 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -48,6 +48,7 @@ + diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 7632fba3a..b2338e0ec 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using Ben.Diagnostics; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration;