Browse Source

Mutations removed.

pull/297/head
Sebastian 8 years ago
parent
commit
bf98fd098b
  1. 7
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  2. 6
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  3. 11
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  4. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQuery.cs
  5. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs
  6. 10
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs
  7. 344
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AppMutationsGraphType.cs
  8. 44
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs
  9. 44
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs
  10. 70
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphInputType.cs
  11. 4
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs
  12. 31
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs
  13. 20
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs
  14. 71
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs
  15. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs
  16. 47
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedInputGraphType.cs
  17. 2
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/GuidGraphType.cs
  18. 73
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InputConverter.cs
  19. 487
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLMutationTests.cs
  20. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

7
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -12,7 +12,6 @@ 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,7 +19,6 @@ 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;
@ -28,20 +26,17 @@ 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(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;
}
@ -58,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var modelContext = await GetModelAsync(context.App);
var ctx = new GraphQLExecutionContext(context, assetRepository, commandBus, contentQuery, urlGenerator);
var ctx = new GraphQLExecutionContext(context, assetRepository, contentQuery, urlGenerator);
return await modelContext.ExecuteAsync(ctx, query);
}

6
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -11,25 +11,19 @@ using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
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 GraphQLExecutionContext : QueryExecutionContext
{
public ICommandBus CommandBus { get; }
public IGraphQLUrlGenerator UrlGenerator { get; }
public GraphQLExecutionContext(QueryContext context,
IAssetRepository assetRepository,
ICommandBus commandBus,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator)
: base(context, assetRepository, contentQuery)
{
CommandBus = commandBus;
UrlGenerator = urlGenerator;
}

11
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -63,7 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
var schemas = model.schemasById.Values;
return new GraphQLSchema { Query = new AppQueriesGraphType(model, schemas), Mutation = new AppMutationsGraphType(model, schemas) };
return new GraphQLSchema { Query = new AppQueriesGraphType(model, schemas) };
}
private void InitializeContentTypes()
@ -142,11 +142,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return field.Accept(new QueryGraphTypeVisitor(schema, GetContentType, this, assetListType));
}
public IGraphType GetInputGraphType(IField field)
{
return field.GetInputGraphType();
}
public IGraphType GetAssetType()
{
return assetType;
@ -173,6 +168,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return null;
}
contentDataTypes.GetOrAdd(schema, s => new ContentDataGraphType());
return contentTypes.GetOrAdd(schema, s => new ContentGraphType());
}
@ -180,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
Guard.NotNull(context, nameof(context));
var inputs = InputConverter.ToInputs(query.Variables);
var inputs = query.Variables?.ToInputs();
var result = await new DocumentExecuter().ExecuteAsync(options =>
{

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQuery.cs

@ -9,7 +9,7 @@ using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public class GraphQLQuery
public sealed class GraphQLQuery
{
public string OperationName { get; set; }

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphModel.cs

@ -35,8 +35,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
IGraphType GetContentDataType(Guid schemaId);
IGraphType GetInputGraphType(IField field);
(IGraphType ResolveType, ValueResolver Resolver) GetGraphType(ISchemaEntity schema, IField field);
}
}

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

@ -36,8 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public static readonly IGraphType References = new ListGraphType<NonNullGraphType<GuidGraphType>>();
public static readonly IGraphType GeolocationInput = new GeolocationInputGraphType();
public static readonly IGraphType NonNullInt = new NonNullGraphType(Int);
public static readonly IGraphType NonNullGuid = new NonNullGraphType(Guid);
@ -52,22 +50,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
public static readonly IGraphType NonNullStatusType = new NonNullGraphType(Status);
public static readonly IGraphType NoopArray = new NoopGraphType("Array");
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(GeolocationInput);
public static readonly IGraphType NoopTags = new NoopGraphType("Tags");
public static readonly IGraphType CommandVersion = new CommandVersionGraphType();
public static readonly IGraphType NoopGeolocation = new NoopGraphType("Geolocation");
}
}

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

@ -1,344 +0,0 @@
// ==========================================================================
// 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;
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<ISchemaEntity> 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 resultType = new ContentDataChangedResultGraphType(schemaType, schemaName, contentDataType);
var inputType = new ContentDataGraphInputType(model, schema);
AddContentCreate(schemaId, schemaType, schemaName, inputType, contentDataType, contentType);
AddContentUpdate(schemaType, schemaName, inputType, resultType);
AddContentPatch(schemaType, schemaName, inputType, resultType);
AddContentPublish(schemaType, schemaName);
AddContentUnpublish(schemaType, schemaName);
AddContentArchive(schemaType, schemaName);
AddContentRestore(schemaType, schemaName);
AddContentDelete(schemaType, schemaName);
}
Description = "The app mutations.";
}
private void AddContentCreate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType contentDataType, IGraphType contentType)
{
AddField(new FieldType
{
Name = $"create{schemaType}Content",
Arguments = new QueryArguments
{
new QueryArgument(AllTypes.None)
{
Name = "data",
Description = $"The data for the {schemaName} content.",
DefaultValue = null,
ResolvedType = new NonNullGraphType(inputType),
},
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,
ResolvedType = AllTypes.Int
}
},
ResolvedType = new NonNullGraphType(contentType),
Resolver = ResolveAsync(async (c, publish) =>
{
var argPublish = c.GetArgument<bool>("publish");
var contentData = GetContentData(c);
var command = new CreateContent { SchemaId = schemaId, Data = contentData, Publish = argPublish };
var commandContext = await publish(command);
var result = commandContext.Result<EntityCreatedResult<NamedContentData>>();
var response = ContentEntity.Create(command, result);
return (IContentEntity)ContentEntity.Create(command, result);
}),
Description = $"Creates an {schemaName} content."
});
}
private void AddContentUpdate(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType)
{
AddField(new FieldType
{
Name = $"update{schemaType}Content",
Arguments = new QueryArguments
{
new QueryArgument(AllTypes.None)
{
Name = "id",
Description = $"The id of the {schemaName} content (GUID)",
DefaultValue = string.Empty,
ResolvedType = AllTypes.NonNullGuid
},
new QueryArgument(AllTypes.None)
{
Name = "data",
Description = $"The data for the {schemaName} content.",
DefaultValue = null,
ResolvedType = new NonNullGraphType(inputType),
},
new QueryArgument(AllTypes.None)
{
Name = "expectedVersion",
Description = "The expected version",
DefaultValue = EtagVersion.Any,
ResolvedType = AllTypes.Int
}
},
ResolvedType = new NonNullGraphType(resultType),
Resolver = ResolveAsync(async (c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c);
var command = new UpdateContent { ContentId = contentId, Data = contentData };
var commandContext = await publish(command);
var result = commandContext.Result<ContentDataChangedResult>();
return result;
}),
Description = $"Update an {schemaName} content by id."
});
}
private void AddContentPatch(string schemaType, string schemaName, ContentDataGraphInputType inputType, IGraphType resultType)
{
AddField(new FieldType
{
Name = $"patch{schemaType}Content",
Arguments = new QueryArguments
{
new QueryArgument(AllTypes.None)
{
Name = "id",
Description = $"The id of the {schemaName} content (GUID)",
DefaultValue = string.Empty,
ResolvedType = AllTypes.NonNullGuid
},
new QueryArgument(AllTypes.None)
{
Name = "data",
Description = $"The data for the {schemaName} content.",
DefaultValue = null,
ResolvedType = new NonNullGraphType(inputType),
},
new QueryArgument(AllTypes.None)
{
Name = "expectedVersion",
Description = "The expected version",
DefaultValue = EtagVersion.Any,
ResolvedType = AllTypes.Int
}
},
ResolvedType = new NonNullGraphType(resultType),
Resolver = ResolveAsync(async (c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var contentData = GetContentData(c);
var command = new PatchContent { ContentId = contentId, Data = contentData };
var commandContext = await publish(command);
var result = commandContext.Result<ContentDataChangedResult>();
return result;
}),
Description = $"Patch a {schemaName} content."
});
}
private void AddContentPublish(string schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"publish{schemaType}Content",
Arguments = CreateIdArguments(schemaName),
ResolvedType = AllTypes.CommandVersion,
Resolver = ResolveAsync((c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Published };
return publish(command);
}),
Description = $"Publish a {schemaName} content."
});
}
private void AddContentUnpublish(string schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"unpublish{schemaType}Content",
Arguments = CreateIdArguments(schemaName),
ResolvedType = AllTypes.CommandVersion,
Resolver = ResolveAsync((c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft };
return publish(command);
}),
Description = $"Unpublish a {schemaName} content."
});
}
private void AddContentArchive(string schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"archive{schemaType}Content",
Arguments = CreateIdArguments(schemaName),
ResolvedType = AllTypes.CommandVersion,
Resolver = ResolveAsync((c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Archived };
return publish(command);
}),
Description = $"Archive a {schemaName} content."
});
}
private void AddContentRestore(string schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"restore{schemaType}Content",
Arguments = CreateIdArguments(schemaName),
ResolvedType = AllTypes.CommandVersion,
Resolver = ResolveAsync((c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var command = new ChangeContentStatus { ContentId = contentId, Status = Status.Draft };
return publish(command);
}),
Description = $"Restore a {schemaName} content."
});
}
private void AddContentDelete(string schemaType, string schemaName)
{
AddField(new FieldType
{
Name = $"delete{schemaType}Content",
Arguments = CreateIdArguments(schemaName),
ResolvedType = AllTypes.CommandVersion,
Resolver = ResolveAsync((c, publish) =>
{
var contentId = c.GetArgument<Guid>("id");
var command = new DeleteContent { ContentId = contentId };
return publish(command);
}),
Description = $"Delete an {schemaName} content."
});
}
private static QueryArguments CreateIdArguments(string schemaName)
{
return new QueryArguments
{
new QueryArgument(AllTypes.None)
{
Name = "id",
Description = $"The id of the {schemaName} content (GUID)",
DefaultValue = string.Empty,
ResolvedType = AllTypes.NonNullGuid
},
new QueryArgument(AllTypes.None)
{
Name = "expectedVersion",
Description = "The expected version",
DefaultValue = EtagVersion.Any,
ResolvedType = AllTypes.Int
}
};
}
private static IFieldResolver ResolveAsync<T>(Func<ResolveFieldContext, Func<SquidexCommand, Task<CommandContext>>, Task<T>> action)
{
return new FuncFieldResolver<Task<T>>(async c =>
{
var e = (GraphQLExecutionContext)c.UserContext;
try
{
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));
throw;
}
catch (DomainException ex)
{
c.Errors.Add(new ExecutionError(ex.Message));
throw;
}
});
}
private static NamedContentData GetContentData(ResolveFieldContext c)
{
return JObject.FromObject(c.GetArgument<object>("data")).ToObject<NamedContentData>();
}
}
}

44
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/CommandVersionGraphType.cs

@ -1,44 +0,0 @@
// ==========================================================================
// 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 : ObjectGraphType<CommandContext>
{
public CommandVersionGraphType()
{
Name = "CommandVersionDto";
AddField(new FieldType
{
Name = "version",
ResolvedType = AllTypes.Int,
Resolver = ResolveVersion(),
Description = "The new version of the item."
});
Description = "The result of a mutation";
}
private static IFieldResolver ResolveVersion()
{
return new FuncFieldResolver<CommandContext, int?>(x =>
{
if (x.Source.Result<object>() is EntitySavedResult result)
{
return (int)result.Version;
}
return null;
});
}
}
}

44
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataChangedResultGraphType.cs

@ -1,44 +0,0 @@
// ==========================================================================
// 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<ContentDataChangedResult>
{
public ContentDataChangedResultGraphType(string schemaType, string schemaName, IGraphType contentDataType)
{
Name = $"{schemaName}DataChangedResultDto";
AddField(new FieldType
{
Name = "version",
ResolvedType = AllTypes.Int,
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<ContentDataChangedResult, object> action)
{
return new FuncFieldResolver<ContentDataChangedResult, object>(c => action(c.Source));
}
}
}

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

@ -1,70 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using GraphQL.Types;
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,
Resolver = null,
ResolvedType = inputType,
Description = field.RawProperties.Hints
});
}
fieldGraphType.Description = $"The input structure of the {fieldName} of a {schemaName} content type.";
AddField(new FieldType
{
Name = field.Name.ToCamelCase(),
Resolver = null,
ResolvedType = fieldGraphType,
Description = $"The {fieldName} field."
});
}
}
Description = $"The structure of a {schemaName} content type.";
}
}
}

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

@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
});
}
fieldGraphType.Description = $"The structure of the {fieldName} field of a {schemaName} content type.";
fieldGraphType.Description = $"The structure of the {fieldName} field of the {schemaName} content type.";
var fieldResolver = new FuncFieldResolver<NamedContentData, IReadOnlyDictionary<string, JToken>>(c =>
{
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}
}
Description = $"The structure of a {schemaName} content type.";
Description = $"The structure of the {schemaName} content type.";
}
}
}

31
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GeolocationInputGraphType.cs

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

20
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldExtensions.cs

@ -1,20 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public static class InputFieldExtensions
{
public static IGraphType GetInputGraphType(this IField field)
{
return field.Accept(InputFieldVisitor.Default);
}
}
}

71
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/InputFieldVisitor.cs

@ -1,71 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class InputFieldVisitor : IFieldVisitor<IGraphType>
{
public static readonly InputFieldVisitor Default = new InputFieldVisitor();
private InputFieldVisitor()
{
}
public IGraphType Visit(IArrayField field)
{
return AllTypes.NoopArray;
}
public IGraphType Visit(IField<AssetsFieldProperties> field)
{
return AllTypes.References;
}
public IGraphType Visit(IField<BooleanFieldProperties> field)
{
return AllTypes.Boolean;
}
public IGraphType Visit(IField<DateTimeFieldProperties> field)
{
return AllTypes.Date;
}
public IGraphType Visit(IField<GeolocationFieldProperties> field)
{
return AllTypes.GeolocationInput;
}
public IGraphType Visit(IField<JsonFieldProperties> field)
{
return AllTypes.Json;
}
public IGraphType Visit(IField<NumberFieldProperties> field)
{
return AllTypes.Float;
}
public IGraphType Visit(IField<ReferencesFieldProperties> field)
{
return AllTypes.References;
}
public IGraphType Visit(IField<StringFieldProperties> field)
{
return AllTypes.String;
}
public IGraphType Visit(IField<TagsFieldProperties> field)
{
return AllTypes.Tags;
}
}
}

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

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
}
}
Description = $"The structure of a {schemaName}.{fieldName} child schema.";
Description = $"The structure of the {schemaName}.{fieldName} nested schema.";
}
}
}

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

@ -1,47 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
{
public sealed class NestedInputGraphType : InputObjectGraphType
{
public NestedInputGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field)
{
var schemaType = schema.TypeName();
var schemaName = schema.DisplayName();
var fieldType = field.TypeName();
var fieldName = field.DisplayName();
Name = $"{schemaType}{fieldName}ChildDto";
foreach (var nestedField in field.Fields.Where(x => !x.IsHidden))
{
var fieldInfo = model.GetGraphType(schema, nestedField);
if (fieldInfo.ResolveType != null)
{
AddField(new FieldType
{
Name = nestedField.Name.ToCamelCase(),
Resolver = null,
ResolvedType = fieldInfo.ResolveType,
Description = $"The {fieldName}/{nestedField.DisplayName()} nested field."
});
}
}
Description = $"The structure of a {schemaName}.{fieldName} nested schema.";
}
}
}

2
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/GuidGraphType.cs → src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/GuidGraphType.cs

@ -9,7 +9,7 @@ using System;
using GraphQL.Language.AST;
using GraphQL.Types;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
public sealed class GuidGraphType : ScalarGraphType
{

73
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InputConverter.cs

@ -1,73 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using GraphQL;
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils
{
public static class InputConverter
{
public static Inputs ToInputs(JObject input)
{
var result = new Inputs();
if (input != null)
{
foreach (var kvp in input)
{
result.Add(kvp.Key, GetValue(kvp.Value, 1));
}
}
return result;
}
private static object GetValue(object value, int level)
{
if (level == 3)
{
return value;
}
switch (value)
{
case JObject jObject:
{
var result = new Dictionary<string, object>();
foreach (var kvp in jObject)
{
result.Add(kvp.Key, GetValue(kvp.Value, level + 1));
}
return result;
}
case JArray jArray:
{
var result = new List<object>();
foreach (var item in jArray)
{
result.Add(GetValue(item, level + 1));
}
return result;
}
case JValue jValue:
{
return jValue.Value;
}
}
return value;
}
}
}

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

@ -1,487 +0,0 @@
// ==========================================================================
// 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.Infrastructure;
using Squidex.Infrastructure.Commands;
using Xunit;
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(), A.Dummy<ICommandBus>());
public GraphQLMutationTests()
{
content = CreateContent(contentId, Guid.NewGuid(), Guid.NewGuid(), null);
A.CallTo(() => commandBus.PublishAsync(A<ICommand>.Ignored))
.Returns(commandContext);
}
[Fact]
public async Task Should_return_single_content_when_creating_content()
{
var query = $@"
mutation OP($data: MySchemaInputDto!) {{
createMySchemaContent(data: $data, expectedVersion: 10) {{
version
data {{
myString {{
de
}}
myNumber {{
iv
}}
myBoolean {{
iv
}}
myDatetime {{
iv
}}
myGeolocation {{
iv
}}
myTags {{
iv
}}
}}
}}
}}";
commandContext.Complete(new EntityCreatedResult<NamedContentData>(content.Data, 13));
var inputContent = GetInputContent(content);
var variables =
new JObject(
new JProperty("data", inputContent));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query, Variables = variables });
var expected = new
{
data = new
{
createMySchemaContent = 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
{
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(context, 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
{
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 query = $@"
mutation OP($data: MySchemaInputDto!) {{
patchMySchemaContent(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(context, new GraphQLQuery { Query = query, Variables = variables });
var expected = new
{
data = new
{
patchMySchemaContent = 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
{
latitude = 10,
longitude = 20
}
},
myTags = new
{
iv = new[]
{
"tag1",
"tag2"
}
}
}
}
}
};
AssertResult(expected, result);
}
[Fact]
public async Task Should_publish_command_for_publish()
{
var query = $@"
mutation {{
publishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{
version
}}
}}";
commandContext.Complete(new EntitySavedResult(13));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var expected = new
{
data = new
{
publishMySchemaContent = new
{
version = 13
}
}
};
AssertResult(expected, result);
A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x =>
x.ContentId == contentId &&
x.Status == Status.Published &&
x.ExpectedVersion == 10)))
.MustHaveHappened();
}
[Fact]
public async Task Should_publish_command_for_unpublish()
{
var query = $@"
mutation {{
unpublishMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{
version
}}
}}";
commandContext.Complete(new EntitySavedResult(13));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var expected = new
{
data = new
{
unpublishMySchemaContent = new
{
version = 13
}
}
};
AssertResult(expected, result);
A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x =>
x.ContentId == contentId &&
x.Status == Status.Draft &&
x.ExpectedVersion == 10)))
.MustHaveHappened();
}
[Fact]
public async Task Should_publish_command_for_archive()
{
var query = $@"
mutation {{
archiveMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{
version
}}
}}";
commandContext.Complete(new EntitySavedResult(13));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var expected = new
{
data = new
{
archiveMySchemaContent = new
{
version = 13
}
}
};
AssertResult(expected, result);
A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x =>
x.ContentId == contentId &&
x.Status == Status.Archived &&
x.ExpectedVersion == 10)))
.MustHaveHappened();
}
[Fact]
public async Task Should_publish_command_for_restore()
{
var query = $@"
mutation {{
restoreMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{
version
}}
}}";
commandContext.Complete(new EntitySavedResult(13));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var expected = new
{
data = new
{
restoreMySchemaContent = new
{
version = 13
}
}
};
AssertResult(expected, result);
A.CallTo(() => commandBus.PublishAsync(
A<ChangeContentStatus>.That.Matches(x =>
x.ContentId == contentId &&
x.Status == Status.Draft &&
x.ExpectedVersion == 10)))
.MustHaveHappened();
}
[Fact]
public async Task Should_publish_command_for_delete()
{
var query = $@"
mutation {{
deleteMySchemaContent(id: ""{contentId}"", expectedVersion: 10) {{
version
}}
}}";
commandContext.Complete(new EntitySavedResult(13));
var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query });
var expected = new
{
data = new
{
deleteMySchemaContent = new
{
version = 13
}
}
};
AssertResult(expected, result);
A.CallTo(() => commandBus.PublishAsync(
A<DeleteContent>.That.Matches(x =>
x.ContentId == contentId &&
x.ExpectedVersion == 10)))
.MustHaveHappened();
}
private static JObject GetInputContent(IContentEntity content)
{
var camelContent = new NamedContentData();
foreach (var kvp in content.Data)
{
camelContent[kvp.Key.ToCamelCase()] = kvp.Value;
}
return JObject.FromObject(camelContent);
}
}
}

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

@ -24,7 +24,6 @@ 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
@ -39,7 +38,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
protected static readonly string appName = "my-app";
protected readonly Schema schemaDef;
protected readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
protected readonly ICommandBus commandBus = A.Fake<ICommandBus>();
protected readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
protected readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
@ -93,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => appProvider.GetSchemasAsync(appId)).Returns(allSchemas);
sut = new CachingGraphQLService(cache, appProvider, assetRepository, commandBus, contentQuery, new FakeUrlGenerator());
sut = new CachingGraphQLService(cache, appProvider, assetRepository, contentQuery, new FakeUrlGenerator());
}
protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null)

Loading…
Cancel
Save