mirror of https://github.com/Squidex/squidex.git
Browse Source
* Refactorings * GraphQL mutations. * Refactoring and fix for json path arguments.pull/575/head
committed by
GitHub
60 changed files with 2945 additions and 1375 deletions
@ -0,0 +1,100 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using GraphQL.Types; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
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 inputType = new ContentDataInputGraphType(schema, schemaName, schemaType, model); |
||||
|
|
||||
|
AddContentCreate(schemaId, schemaType, schemaName, inputType, contentType); |
||||
|
AddContentUpdate(schemaType, schemaName, inputType, contentType); |
||||
|
AddContentPatch(schemaType, schemaName, inputType, contentType); |
||||
|
AddContentChangeStatus(schemaType, schemaName, contentType); |
||||
|
AddContentDelete(schemaType, schemaName); |
||||
|
} |
||||
|
|
||||
|
Description = "The app mutations."; |
||||
|
} |
||||
|
|
||||
|
private void AddContentCreate(NamedId<Guid> schemaId, string schemaType, string schemaName, ContentDataInputGraphType inputType, IGraphType contentType) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = $"create{schemaType}Content", |
||||
|
Arguments = ContentActions.Create.Arguments(inputType), |
||||
|
ResolvedType = contentType, |
||||
|
Resolver = ContentActions.Create.Resolver(schemaId), |
||||
|
Description = $"Creates an {schemaName} content." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void AddContentUpdate(string schemaType, string schemaName, ContentDataInputGraphType inputType, IGraphType contentType) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = $"update{schemaType}Content", |
||||
|
Arguments = ContentActions.UpdateOrPatch.Arguments(inputType), |
||||
|
ResolvedType = contentType, |
||||
|
Resolver = ContentActions.UpdateOrPatch.Update, |
||||
|
Description = $"Update an {schemaName} content by id." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void AddContentPatch(string schemaType, string schemaName, ContentDataInputGraphType inputType, IGraphType contentType) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = $"patch{schemaType}Content", |
||||
|
Arguments = ContentActions.UpdateOrPatch.Arguments(inputType), |
||||
|
ResolvedType = contentType, |
||||
|
Resolver = ContentActions.UpdateOrPatch.Patch, |
||||
|
Description = $"Patch an {schemaName} content by id." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void AddContentChangeStatus(string schemaType, string schemaName, IGraphType contentType) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = $"publish{schemaType}Content", |
||||
|
Arguments = ContentActions.ChangeStatus.Arguments, |
||||
|
ResolvedType = contentType, |
||||
|
Resolver = ContentActions.ChangeStatus.Resolver, |
||||
|
Description = $"Publish a {schemaName} content." |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void AddContentDelete(string schemaType, string schemaName) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = $"delete{schemaType}Content", |
||||
|
Arguments = ContentActions.Delete.Arguments, |
||||
|
ResolvedType = EntitySavedGraphType.NonNull, |
||||
|
Resolver = ContentActions.Delete.Resolver, |
||||
|
Description = $"Delete an {schemaName} content." |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,112 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using GraphQL; |
||||
|
using GraphQL.Resolvers; |
||||
|
using GraphQL.Types; |
||||
|
using Squidex.Domain.Apps.Entities.Assets; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class AssetActions |
||||
|
{ |
||||
|
public static class Metadata |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "path", |
||||
|
Description = "The path to the json value", |
||||
|
DefaultValue = null, |
||||
|
ResolvedType = AllTypes.String |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = new FuncFieldResolver<IEnrichedAssetEntity, object?>(c => |
||||
|
{ |
||||
|
if (c.Arguments.TryGetValue("path", out var path)) |
||||
|
{ |
||||
|
c.Source.Metadata.TryGetByPath(path as string, out var result); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return c.Source.Metadata; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static class Find |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "id", |
||||
|
Description = "The id of the asset (GUID).", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullGuid |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = new FuncFieldResolver<object?>(c => |
||||
|
{ |
||||
|
var id = c.GetArgument<Guid>("id"); |
||||
|
|
||||
|
return ((GraphQLExecutionContext)c.UserContext).FindAssetAsync(id); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static class Query |
||||
|
{ |
||||
|
private static QueryArguments? resolver; |
||||
|
|
||||
|
public static QueryArguments Arguments(int pageSize) |
||||
|
{ |
||||
|
return resolver ??= new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "top", |
||||
|
Description = $"Optional number of assets to take (Default: {pageSize}).", |
||||
|
DefaultValue = pageSize, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "skip", |
||||
|
Description = "Optional number of assets to skip.", |
||||
|
DefaultValue = 0, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "filter", |
||||
|
Description = "Optional OData filter.", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.String |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "orderby", |
||||
|
Description = "Optional OData order definition.", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.String |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = new FuncFieldResolver<object?>(c => |
||||
|
{ |
||||
|
var query = c.BuildODataQuery(); |
||||
|
|
||||
|
return ((GraphQLExecutionContext)c.UserContext).QueryAssetsAsync(query); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using GraphQL; |
||||
|
using GraphQL.Resolvers; |
||||
|
using Squidex.Domain.Apps.Core.Assets; |
||||
|
using Squidex.Domain.Apps.Entities.Assets; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class AssetResolvers |
||||
|
{ |
||||
|
public static readonly IFieldResolver Url = Resolve((asset, _, context) => |
||||
|
{ |
||||
|
return context.UrlGenerator.AssetContent(asset.Id); |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver SourceUrl = Resolve((asset, _, context) => |
||||
|
{ |
||||
|
return context.UrlGenerator.AssetSource(asset.Id, asset.FileVersion); |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver ThumbnailUrl = Resolve((asset, _, context) => |
||||
|
{ |
||||
|
return context.UrlGenerator.AssetThumbnail(asset.Id, asset.Type); |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver FileHash = Resolve(x => x.FileHash); |
||||
|
public static readonly IFieldResolver FileName = Resolve(x => x.FileName); |
||||
|
public static readonly IFieldResolver FileSize = Resolve(x => x.FileSize); |
||||
|
public static readonly IFieldResolver FileType = Resolve(x => x.FileName.FileType()); |
||||
|
public static readonly IFieldResolver FileVersion = Resolve(x => x.FileVersion); |
||||
|
public static readonly IFieldResolver IsImage = Resolve(x => x.Type == AssetType.Image); |
||||
|
public static readonly IFieldResolver IsProtected = Resolve(x => x.IsProtected); |
||||
|
public static readonly IFieldResolver ListTotal = ResolveList(x => x.Total); |
||||
|
public static readonly IFieldResolver ListItems = ResolveList(x => x); |
||||
|
public static readonly IFieldResolver MetadataText = Resolve(x => x.MetadataText); |
||||
|
public static readonly IFieldResolver MimeType = Resolve(x => x.MimeType); |
||||
|
public static readonly IFieldResolver PixelHeight = Resolve(x => x.Metadata.GetPixelHeight()); |
||||
|
public static readonly IFieldResolver PixelWidth = Resolve(x => x.Metadata.GetPixelWidth()); |
||||
|
public static readonly IFieldResolver Slug = Resolve(x => x.Slug); |
||||
|
public static readonly IFieldResolver Tags = Resolve(x => x.TagNames); |
||||
|
public static readonly IFieldResolver Type = Resolve(x => x.Type); |
||||
|
|
||||
|
private static IFieldResolver Resolve<T>(Func<IEnrichedAssetEntity, IResolveFieldContext, GraphQLExecutionContext, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IEnrichedAssetEntity, object?>(c => action(c.Source, c, (GraphQLExecutionContext)c.UserContext)); |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver Resolve<T>(Func<IEnrichedAssetEntity, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IEnrichedAssetEntity, object?>(c => action(c.Source)); |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver ResolveList<T>(Func<IResultList<IEnrichedAssetEntity>, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IResultList<IEnrichedAssetEntity>, object?>(c => action(c.Source)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,336 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Commands; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Commands; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
using Squidex.Infrastructure.Validation; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class ContentActions |
||||
|
{ |
||||
|
public static class Json |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "path", |
||||
|
Description = "The path to the json value", |
||||
|
DefaultValue = null, |
||||
|
ResolvedType = AllTypes.String |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly ValueResolver Resolver = new ValueResolver((value, c) => |
||||
|
{ |
||||
|
if (c.Arguments.TryGetValue("path", out var p) && p is string path) |
||||
|
{ |
||||
|
value.TryGetByPath(path, out var result); |
||||
|
|
||||
|
return result!; |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static readonly QueryArguments JsonPath = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "path", |
||||
|
Description = "The path to the json value", |
||||
|
DefaultValue = null, |
||||
|
ResolvedType = AllTypes.String |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static class Find |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "id", |
||||
|
Description = "The id of the content (GUID).", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullGuid |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = new FuncFieldResolver<object?>(c => |
||||
|
{ |
||||
|
var id = c.GetArgument<Guid>("id"); |
||||
|
|
||||
|
return ((GraphQLExecutionContext)c.UserContext).FindContentAsync(id); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static class Query |
||||
|
{ |
||||
|
private static QueryArguments? arguments; |
||||
|
|
||||
|
public static QueryArguments Arguments(int pageSize) |
||||
|
{ |
||||
|
return arguments ??= new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "top", |
||||
|
Description = $"Optional number of contents to take (Default: {pageSize}).", |
||||
|
DefaultValue = pageSize, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "skip", |
||||
|
Description = "Optional number of contents to skip.", |
||||
|
DefaultValue = 0, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "filter", |
||||
|
Description = "Optional OData filter.", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.String |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "orderby", |
||||
|
Description = "Optional OData order definition.", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.String |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "search", |
||||
|
Description = "Optional OData full text search.", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.String |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static IFieldResolver Resolver(Guid schemaId) |
||||
|
{ |
||||
|
var schemaIdValue = schemaId.ToString(); |
||||
|
|
||||
|
return new FuncFieldResolver<object?>(c => |
||||
|
{ |
||||
|
var query = c.BuildODataQuery(); |
||||
|
|
||||
|
return ((GraphQLExecutionContext)c.UserContext).QueryContentsAsync(schemaIdValue, query); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static class Create |
||||
|
{ |
||||
|
public static QueryArguments Arguments(IGraphType inputType) |
||||
|
{ |
||||
|
return new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "data", |
||||
|
Description = "The data for the 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 |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static IFieldResolver Resolver(NamedId<Guid> schemaId) |
||||
|
{ |
||||
|
return ResolveAsync<IEnrichedContentEntity>(c => |
||||
|
{ |
||||
|
var contentPublish = c.GetArgument<bool>("publish"); |
||||
|
var contentData = GetContentData(c); |
||||
|
|
||||
|
return new CreateContent { SchemaId = schemaId, Data = contentData, Publish = contentPublish }; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static class UpdateOrPatch |
||||
|
{ |
||||
|
public static QueryArguments Arguments(IGraphType inputType) |
||||
|
{ |
||||
|
return new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "id", |
||||
|
Description = "The id of the content (GUID)", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullGuid |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "data", |
||||
|
Description = "The data for the content.", |
||||
|
DefaultValue = null, |
||||
|
ResolvedType = new NonNullGraphType(inputType), |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "expectedVersion", |
||||
|
Description = "The expected version", |
||||
|
DefaultValue = EtagVersion.Any, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static readonly IFieldResolver Update = ResolveAsync<IEnrichedContentEntity>(c => |
||||
|
{ |
||||
|
var contentId = c.GetArgument<Guid>("id"); |
||||
|
var contentData = GetContentData(c); |
||||
|
|
||||
|
return new UpdateContent { ContentId = contentId, Data = contentData }; |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver Patch = ResolveAsync<IEnrichedContentEntity>(c => |
||||
|
{ |
||||
|
var contentId = c.GetArgument<Guid>("id"); |
||||
|
var contentData = GetContentData(c); |
||||
|
|
||||
|
return new PatchContent { ContentId = contentId, Data = contentData }; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static class ChangeStatus |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "id", |
||||
|
Description = "The id of the content (GUID)", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullGuid |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "status", |
||||
|
Description = "The new status", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullString |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "dueTime", |
||||
|
Description = "When to change the status", |
||||
|
DefaultValue = EtagVersion.Any, |
||||
|
ResolvedType = AllTypes.Date |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "expectedVersion", |
||||
|
Description = "The expected version", |
||||
|
DefaultValue = EtagVersion.Any, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = ResolveAsync<IEnrichedContentEntity>(c => |
||||
|
{ |
||||
|
var contentId = c.GetArgument<Guid>("id"); |
||||
|
var contentStatus = new Status(c.GetArgument<string>("status")); |
||||
|
var contentDueTime = c.GetArgument<Instant?>("dueTime"); |
||||
|
|
||||
|
return new ChangeContentStatus { ContentId = contentId, Status = contentStatus, DueTime = contentDueTime }; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static class Delete |
||||
|
{ |
||||
|
public static readonly QueryArguments Arguments = new QueryArguments |
||||
|
{ |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "id", |
||||
|
Description = "The id of the content (GUID)", |
||||
|
DefaultValue = string.Empty, |
||||
|
ResolvedType = AllTypes.NonNullGuid |
||||
|
}, |
||||
|
new QueryArgument(AllTypes.None) |
||||
|
{ |
||||
|
Name = "expectedVersion", |
||||
|
Description = "The expected version", |
||||
|
DefaultValue = EtagVersion.Any, |
||||
|
ResolvedType = AllTypes.Int |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
public static readonly IFieldResolver Resolver = ResolveAsync<EntitySavedResult>(c => |
||||
|
{ |
||||
|
var contentId = c.GetArgument<Guid>("id"); |
||||
|
|
||||
|
return new DeleteContent { ContentId = contentId }; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static NamedContentData GetContentData(IResolveFieldContext c) |
||||
|
{ |
||||
|
var source = c.GetArgument<IDictionary<string, object>>("data"); |
||||
|
|
||||
|
return source.ToNamedContentData((IComplexGraphType)c.FieldDefinition.Arguments.Find("data").Flatten()); |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver ResolveAsync<T>(Func<IResolveFieldContext, SquidexCommand> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<Task<T>>(async c => |
||||
|
{ |
||||
|
var e = (GraphQLExecutionContext)c.UserContext; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var command = action(c); |
||||
|
|
||||
|
command.ExpectedVersion = c.GetArgument("expectedVersion", EtagVersion.Any); |
||||
|
|
||||
|
var commandContext = await e.CommandBus.PublishAsync(command); |
||||
|
|
||||
|
return commandContext.Result<T>(); |
||||
|
} |
||||
|
catch (ValidationException ex) |
||||
|
{ |
||||
|
c.Errors.Add(new ExecutionError(ex.Message)); |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
catch (DomainException ex) |
||||
|
{ |
||||
|
c.Errors.Add(new ExecutionError(ex.Message)); |
||||
|
|
||||
|
throw; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public sealed class ContentDataInputGraphType : InputObjectGraphType |
||||
|
{ |
||||
|
public ContentDataInputGraphType(ISchemaEntity schema, string schemaName, string schemaType, IGraphModel model) |
||||
|
{ |
||||
|
Name = $"{schemaType}DataInputDto"; |
||||
|
|
||||
|
foreach (var (field, fieldName, typeName) in schema.SchemaDef.Fields.SafeFields().Where(x => x.Field.IsForApi(true))) |
||||
|
{ |
||||
|
var resolvedType = model.GetInputGraphType(schema, field, typeName); |
||||
|
|
||||
|
if (resolvedType != null) |
||||
|
{ |
||||
|
var displayName = field.DisplayName(); |
||||
|
|
||||
|
var fieldGraphType = new InputObjectGraphType |
||||
|
{ |
||||
|
Name = $"{schemaType}Data{typeName}InputDto" |
||||
|
}; |
||||
|
|
||||
|
var partitioning = model.ResolvePartition(field.Partitioning); |
||||
|
|
||||
|
foreach (var partitionKey in partitioning.AllKeys) |
||||
|
{ |
||||
|
fieldGraphType.AddField(new FieldType |
||||
|
{ |
||||
|
Name = partitionKey.EscapePartition(), |
||||
|
Resolver = null, |
||||
|
ResolvedType = resolvedType, |
||||
|
Description = field.RawProperties.Hints |
||||
|
}).WithSourceName( partitionKey); |
||||
|
} |
||||
|
|
||||
|
fieldGraphType.Description = $"The structure of the {displayName} field of the {schemaName} content input type."; |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = fieldName, |
||||
|
Resolver = null, |
||||
|
ResolvedType = fieldGraphType, |
||||
|
Description = $"The {displayName} field." |
||||
|
}).WithSourceName(field.Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Description = $"The structure of the {schemaName} data input type."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using GraphQL; |
||||
|
using GraphQL.Resolvers; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.ConvertContent; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class ContentResolvers |
||||
|
{ |
||||
|
public static IFieldResolver NestedValue(ValueResolver valueResolver, string key) |
||||
|
{ |
||||
|
return new FuncFieldResolver<JsonObject, object?>(c => |
||||
|
{ |
||||
|
if (c.Source.TryGetValue(key, out var value)) |
||||
|
{ |
||||
|
return valueResolver(value, c); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static IFieldResolver Partition(ValueResolver valueResolver, string key) |
||||
|
{ |
||||
|
return new FuncFieldResolver<ContentFieldData, object?>(c => |
||||
|
{ |
||||
|
if (c.Source.TryGetValue(key, out var value) && value != null) |
||||
|
{ |
||||
|
return valueResolver(value, c); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static IFieldResolver FlatPartition(ValueResolver valueResolver, string key) |
||||
|
{ |
||||
|
return new FuncFieldResolver<FlatContentData, object?>(c => |
||||
|
{ |
||||
|
if (c.Source.TryGetValue(key, out var value) && value != null) |
||||
|
{ |
||||
|
return valueResolver(value, c); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static IFieldResolver Field(RootField field) |
||||
|
{ |
||||
|
var fieldName = field.Name; |
||||
|
|
||||
|
return new FuncFieldResolver<NamedContentData, IReadOnlyDictionary<string, IJsonValue>?>(c => |
||||
|
{ |
||||
|
return c.Source?.GetOrDefault(fieldName); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static readonly IFieldResolver Url = Resolve((content, _, context) => |
||||
|
{ |
||||
|
var appId = content.AppId; |
||||
|
|
||||
|
return context.UrlGenerator.ContentUI(appId, content.SchemaId, content.Id); |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver FlatData = Resolve((content, c, context) => |
||||
|
{ |
||||
|
var language = context.Context.App.LanguagesConfig.Master; |
||||
|
|
||||
|
return content.Data.ToFlatten(language); |
||||
|
}); |
||||
|
|
||||
|
public static readonly IFieldResolver Data = Resolve(x => x.Data); |
||||
|
public static readonly IFieldResolver Status = Resolve(x => x.Status.Name.ToUpperInvariant()); |
||||
|
public static readonly IFieldResolver StatusColor = Resolve(x => x.StatusColor); |
||||
|
public static readonly IFieldResolver ListTotal = ResolveList(x => x.Total); |
||||
|
public static readonly IFieldResolver ListItems = ResolveList(x => x); |
||||
|
|
||||
|
private static IFieldResolver Resolve<T>(Func<IEnrichedContentEntity, IResolveFieldContext, GraphQLExecutionContext, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IEnrichedContentEntity, object?>(c => action(c.Source, c, (GraphQLExecutionContext)c.UserContext)); |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver Resolve<T>(Func<IEnrichedContentEntity, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IEnrichedContentEntity, object?>(c => action(c.Source)); |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver ResolveList<T>(Func<IResultList<IEnrichedContentEntity>, T> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<IResultList<IEnrichedContentEntity>, object?>(c => action(c.Source)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using GraphQL.Resolvers; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class EntityResolvers |
||||
|
{ |
||||
|
public static readonly IFieldResolver Id = Resolve<IEntity>(x => x.Id.ToString()); |
||||
|
public static readonly IFieldResolver Created = Resolve<IEntity>(x => x.Created); |
||||
|
public static readonly IFieldResolver CreatedBy = Resolve<IEntityWithCreatedBy>(x => x.CreatedBy.ToString()); |
||||
|
public static readonly IFieldResolver LastModified = Resolve<IEntity>(x => x.LastModified.ToString()); |
||||
|
public static readonly IFieldResolver LastModifiedBy = Resolve<IEntityWithLastModifiedBy>(x => x.LastModifiedBy.ToString()); |
||||
|
public static readonly IFieldResolver Version = Resolve<IEntityWithVersion>(x => x.Version); |
||||
|
|
||||
|
private static IFieldResolver Resolve<TSource>(Func<TSource, object> action) |
||||
|
{ |
||||
|
return new FuncFieldResolver<TSource, object?>(c => action(c.Source)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,85 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public sealed class InputFieldVisitor : IFieldVisitor<IGraphType?> |
||||
|
{ |
||||
|
private readonly ISchemaEntity schema; |
||||
|
private readonly IGraphModel model; |
||||
|
private readonly string fieldName; |
||||
|
|
||||
|
public InputFieldVisitor(ISchemaEntity schema, IGraphModel model, string fieldName) |
||||
|
{ |
||||
|
this.model = model; |
||||
|
this.schema = schema; |
||||
|
this.fieldName = fieldName; |
||||
|
} |
||||
|
|
||||
|
public IGraphType? Visit(IArrayField field) |
||||
|
{ |
||||
|
var schemaFieldType = new ListGraphType(new NonNullGraphType(new NestedInputGraphType(model, schema, field, fieldName))); |
||||
|
|
||||
|
return schemaFieldType; |
||||
|
} |
||||
|
|
||||
|
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 GeolocationInputGraphType.Nullable; |
||||
|
} |
||||
|
|
||||
|
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.Json; |
||||
|
} |
||||
|
|
||||
|
public IGraphType? Visit(IField<StringFieldProperties> field) |
||||
|
{ |
||||
|
return AllTypes.String; |
||||
|
} |
||||
|
|
||||
|
public IGraphType? Visit(IField<TagsFieldProperties> field) |
||||
|
{ |
||||
|
return AllTypes.Tags; |
||||
|
} |
||||
|
|
||||
|
public IGraphType? Visit(IField<UIFieldProperties> field) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using GraphQL.Types; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public sealed class NestedInputGraphType : InputObjectGraphType |
||||
|
{ |
||||
|
public NestedInputGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field, string fieldName) |
||||
|
{ |
||||
|
var schemaType = schema.TypeName(); |
||||
|
var schemaName = schema.DisplayName(); |
||||
|
|
||||
|
var fieldDisplayName = field.DisplayName(); |
||||
|
|
||||
|
Name = $"{schemaType}{fieldName}InputChildDto"; |
||||
|
|
||||
|
foreach (var (nestedField, nestedName, typeName) in field.Fields.SafeFields().Where(x => x.Field.IsForApi(true))) |
||||
|
{ |
||||
|
var resolvedType = model.GetInputGraphType(schema, nestedField, typeName); |
||||
|
|
||||
|
if (resolvedType != null) |
||||
|
{ |
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = nestedName, |
||||
|
Resolver = null, |
||||
|
ResolvedType = resolvedType, |
||||
|
Description = $"The {fieldDisplayName}/{nestedField.DisplayName()} nested field." |
||||
|
}).WithSourceName(nestedField.Name); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Description = $"The structure of the {schemaName}.{fieldDisplayName} nested schema."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using GraphQL.Types; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public static class Converters |
||||
|
{ |
||||
|
public static NamedContentData ToNamedContentData(this IDictionary<string, object> source, IComplexGraphType type) |
||||
|
{ |
||||
|
var result = new NamedContentData(); |
||||
|
|
||||
|
foreach (var field in type.Fields) |
||||
|
{ |
||||
|
if (source.TryGetValue(field.Name, out var t) && t is IDictionary<string, object> nested && field.ResolvedType is IComplexGraphType complexType) |
||||
|
{ |
||||
|
result[field.GetSourceName()] = nested.ToFieldData(complexType); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static ContentFieldData ToFieldData(this IDictionary<string, object> source, IComplexGraphType type) |
||||
|
{ |
||||
|
var result = new ContentFieldData(); |
||||
|
|
||||
|
foreach (var field in type.Fields) |
||||
|
{ |
||||
|
if (source.TryGetValue(field.Name, out var value)) |
||||
|
{ |
||||
|
if (value is List<object> list && field.ResolvedType.Flatten() is IComplexGraphType nestedType) |
||||
|
{ |
||||
|
var arr = new JsonArray(); |
||||
|
|
||||
|
foreach (var item in list) |
||||
|
{ |
||||
|
if (item is IDictionary<string, object> nested) |
||||
|
{ |
||||
|
arr.Add(nested.ToNestedData(nestedType)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
result[field.GetSourceName()] = arr; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
result[field.GetSourceName()] = JsonConverter.ParseJson(value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public static IJsonValue ToNestedData(this IDictionary<string, object> source, IComplexGraphType type) |
||||
|
{ |
||||
|
var result = JsonValue.Object(); |
||||
|
|
||||
|
foreach (var field in type.Fields) |
||||
|
{ |
||||
|
if (source.TryGetValue(field.Name, out var value)) |
||||
|
{ |
||||
|
result[field.GetSourceName()] = JsonConverter.ParseJson(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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 EntitySavedGraphType : ObjectGraphType<EntitySavedResult> |
||||
|
{ |
||||
|
public static readonly IGraphType Nullable = new EntitySavedGraphType(); |
||||
|
|
||||
|
public static readonly IGraphType NonNull = new NonNullGraphType(Nullable); |
||||
|
|
||||
|
private EntitySavedGraphType() |
||||
|
{ |
||||
|
Name = "EntitySavedResultDto"; |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = "version", |
||||
|
Resolver = ResolveVersion(), |
||||
|
ResolvedType = AllTypes.NonNullLong, |
||||
|
Description = "The new version of the item." |
||||
|
}); |
||||
|
|
||||
|
Description = "The result of a mutation"; |
||||
|
} |
||||
|
|
||||
|
private static IFieldResolver ResolveVersion() |
||||
|
{ |
||||
|
return new FuncFieldResolver<EntitySavedResult, long>(x => |
||||
|
{ |
||||
|
return x.Source.Version; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Types; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public sealed class GeolocationInputGraphType : InputObjectGraphType |
||||
|
{ |
||||
|
public static readonly IGraphType Nullable = new GeolocationInputGraphType(); |
||||
|
|
||||
|
public static readonly IGraphType NonNull = new NonNullGraphType(Nullable); |
||||
|
|
||||
|
private GeolocationInputGraphType() |
||||
|
{ |
||||
|
Name = "GeolocationInputDto"; |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = "latitude", |
||||
|
ResolvedType = AllTypes.NonNullFloat |
||||
|
}); |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = "longitude", |
||||
|
ResolvedType = AllTypes.NonNullFloat |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,55 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// 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.Utils |
|
||||
{ |
|
||||
public sealed class GuidGraphType2 : ScalarGraphType |
|
||||
{ |
|
||||
public GuidGraphType2() |
|
||||
{ |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Language.AST; |
||||
|
using GraphQL.Types; |
||||
|
using NodaTime; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils |
||||
|
{ |
||||
|
public sealed class InstantConverter : IAstFromValueConverter |
||||
|
{ |
||||
|
public static readonly InstantConverter Instance = new InstantConverter(); |
||||
|
|
||||
|
private InstantConverter() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public IValue Convert(object value, IGraphType type) |
||||
|
{ |
||||
|
return new InstantValueNode((Instant)value); |
||||
|
} |
||||
|
|
||||
|
public bool Matches(object value, IGraphType type) |
||||
|
{ |
||||
|
return type is InstantGraphType; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,104 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL |
||||
|
{ |
||||
|
public class GraphQLIntrospectionTests : GraphQLTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Should_introspect() |
||||
|
{ |
||||
|
const string query = @"
|
||||
|
query IntrospectionQuery { |
||||
|
__schema { |
||||
|
queryType { name } |
||||
|
mutationType { name } |
||||
|
subscriptionType { name } |
||||
|
types { |
||||
|
...FullType |
||||
|
} |
||||
|
directives { |
||||
|
name |
||||
|
description |
||||
|
args { |
||||
|
...InputValue |
||||
|
} |
||||
|
onOperation |
||||
|
onFragment |
||||
|
onField |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fragment FullType on __Type { |
||||
|
kind |
||||
|
name |
||||
|
description |
||||
|
fields(includeDeprecated: true) { |
||||
|
name |
||||
|
description |
||||
|
args { |
||||
|
...InputValue |
||||
|
} |
||||
|
type { |
||||
|
...TypeRef |
||||
|
} |
||||
|
isDeprecated |
||||
|
deprecationReason |
||||
|
} |
||||
|
inputFields { |
||||
|
...InputValue |
||||
|
} |
||||
|
interfaces { |
||||
|
...TypeRef |
||||
|
} |
||||
|
enumValues(includeDeprecated: true) { |
||||
|
name |
||||
|
description |
||||
|
isDeprecated |
||||
|
deprecationReason |
||||
|
} |
||||
|
possibleTypes { |
||||
|
...TypeRef |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fragment InputValue on __InputValue { |
||||
|
name |
||||
|
description |
||||
|
type { ...TypeRef } |
||||
|
defaultValue |
||||
|
} |
||||
|
|
||||
|
fragment TypeRef on __Type { |
||||
|
kind |
||||
|
name |
||||
|
ofType { |
||||
|
kind |
||||
|
name |
||||
|
ofType { |
||||
|
kind |
||||
|
name |
||||
|
ofType { |
||||
|
kind |
||||
|
name |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}";
|
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query, OperationName = "IntrospectionQuery" }); |
||||
|
|
||||
|
var json = serializer.Serialize(result.Response, true); |
||||
|
|
||||
|
Assert.NotEmpty(json); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,319 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ================================ ==========================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text.RegularExpressions; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using GraphQL; |
||||
|
using GraphQL.NewtonsoftJson; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using NodaTime; |
||||
|
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 IEnrichedContentEntity content; |
||||
|
private readonly CommandContext commandContext = new CommandContext(new PatchContent(), A.Dummy<ICommandBus>()); |
||||
|
|
||||
|
public GraphQLMutationTests() |
||||
|
{ |
||||
|
content = TestContent.Create(schemaId, contentId, schemaRefId1.Id, schemaRefId2.Id, null); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync(A<ICommand>.Ignored)) |
||||
|
.Returns(commandContext); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_creating_content() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation { |
||||
|
createMySchemaContent(data: <DATA>) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<DATA>", GetDataString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
createMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<CreateContent>.That.Matches(x => |
||||
|
x.SchemaId.Equals(schemaId) && |
||||
|
x.ExpectedVersion == EtagVersion.Any && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_creating_content_with_variable() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation OP($data: MySchemaDataInputDto!) { |
||||
|
createMySchemaContent(data: $data) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query, Inputs = GetInput() }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
createMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<CreateContent>.That.Matches(x => |
||||
|
x.SchemaId.Equals(schemaId) && |
||||
|
x.ExpectedVersion == EtagVersion.Any && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_updating_content() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation { |
||||
|
updateMySchemaContent(id: ""<ID>"", data: <DATA>, expectedVersion: 10) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString()).Replace("<DATA>", GetDataString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
updateMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<UpdateContent>.That.Matches(x => |
||||
|
x.ContentId == content.Id && |
||||
|
x.ExpectedVersion == 10 && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_updating_content_with_variable() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation OP($data: MySchemaDataInputDto!) { |
||||
|
updateMySchemaContent(id: ""<ID>"", data: $data, expectedVersion: 10) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query, Inputs = GetInput() }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
updateMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<UpdateContent>.That.Matches(x => |
||||
|
x.ContentId == content.Id && |
||||
|
x.ExpectedVersion == 10 && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_patching_content() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation { |
||||
|
patchMySchemaContent(id: ""<ID>"", data: <DATA>, expectedVersion: 10) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString()).Replace("<DATA>", GetDataString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
patchMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<PatchContent>.That.Matches(x => |
||||
|
x.ContentId == content.Id && |
||||
|
x.ExpectedVersion == 10 && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_return_single_content_when_patching_content_with_variable() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation OP($data: MySchemaDataInputDto!) { |
||||
|
patchMySchemaContent(id: ""<ID>"", data: $data, expectedVersion: 10) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query, Inputs = GetInput() }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
patchMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<PatchContent>.That.Matches(x => |
||||
|
x.ContentId == content.Id && |
||||
|
x.ExpectedVersion == 10 && |
||||
|
x.Data.Equals(content.Data)))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_publish_command_for_status_change() |
||||
|
{ |
||||
|
var dueTime = SystemClock.Instance.GetCurrentInstant().WithoutMs(); |
||||
|
|
||||
|
var query = @"
|
||||
|
mutation { |
||||
|
publishMySchemaContent(id: ""<ID>"", status: ""Published"", dueTime: ""<TIME>"", expectedVersion: 10) { |
||||
|
<FIELDS> |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString()).Replace("<TIME>", dueTime.ToString()).Replace("<FIELDS>", TestContent.AllFields);
|
||||
|
|
||||
|
commandContext.Complete(content); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, new GraphQLQuery { Query = query }); |
||||
|
|
||||
|
var expected = new |
||||
|
{ |
||||
|
data = new |
||||
|
{ |
||||
|
publishMySchemaContent = TestContent.Response(content) |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
AssertResult(expected, result); |
||||
|
|
||||
|
A.CallTo(() => commandBus.PublishAsync( |
||||
|
A<ChangeContentStatus>.That.Matches(x => |
||||
|
x.ContentId == contentId && |
||||
|
x.DueTime == dueTime && |
||||
|
x.ExpectedVersion == 10 && |
||||
|
x.Status == Status.Published))) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_publish_command_for_delete() |
||||
|
{ |
||||
|
var query = @"
|
||||
|
mutation { |
||||
|
deleteMySchemaContent(id: ""<ID>"", expectedVersion: 10) { |
||||
|
version |
||||
|
} |
||||
|
}".Replace("<ID>", contentId.ToString());
|
||||
|
|
||||
|
commandContext.Complete(new EntitySavedResult(13)); |
||||
|
|
||||
|
var result = await sut.QueryAsync(requestContext, 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 Inputs GetInput() |
||||
|
{ |
||||
|
var input = new |
||||
|
{ |
||||
|
data = TestContent.Data(content, schemaRefId1.Id, schemaRefId2.Id) |
||||
|
}; |
||||
|
|
||||
|
return JObject.FromObject(input).ToInputs(); |
||||
|
} |
||||
|
|
||||
|
private string GetDataString() |
||||
|
{ |
||||
|
var data = TestContent.Data(content, schemaRefId1.Id, schemaRefId2.Id); |
||||
|
|
||||
|
var json = JsonConvert.SerializeObject(data); |
||||
|
|
||||
|
return Regex.Replace(json, "\"([^\"]+)\":", x => x.Groups[1].Value + ":"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.Assets; |
||||
|
using Squidex.Domain.Apps.Entities.Assets; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL |
||||
|
{ |
||||
|
public static class TestAsset |
||||
|
{ |
||||
|
public const string AllFields = @"
|
||||
|
id |
||||
|
version |
||||
|
created |
||||
|
createdBy |
||||
|
lastModified |
||||
|
lastModifiedBy |
||||
|
url |
||||
|
thumbnailUrl |
||||
|
sourceUrl |
||||
|
mimeType |
||||
|
fileName |
||||
|
fileHash |
||||
|
fileSize |
||||
|
fileVersion |
||||
|
isImage |
||||
|
isProtected |
||||
|
pixelWidth |
||||
|
pixelHeight |
||||
|
tags |
||||
|
type |
||||
|
metadataText |
||||
|
metadataPixelWidth: metadata(path: ""pixelWidth"") |
||||
|
metadataUnknown: metadata(path: ""unknown"") |
||||
|
metadata |
||||
|
slug";
|
||||
|
|
||||
|
public static IEnrichedAssetEntity Create(Guid id) |
||||
|
{ |
||||
|
var now = SystemClock.Instance.GetCurrentInstant(); |
||||
|
|
||||
|
var asset = new AssetEntity |
||||
|
{ |
||||
|
Id = id, |
||||
|
Version = 1, |
||||
|
Created = now, |
||||
|
CreatedBy = new RefToken(RefTokenType.Subject, "user1"), |
||||
|
LastModified = now, |
||||
|
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"), |
||||
|
FileName = "MyFile.png", |
||||
|
Slug = "myfile.png", |
||||
|
FileSize = 1024, |
||||
|
FileHash = "ABC123", |
||||
|
FileVersion = 123, |
||||
|
MimeType = "image/png", |
||||
|
Type = AssetType.Image, |
||||
|
MetadataText = "metadata-text", |
||||
|
Metadata = |
||||
|
new AssetMetadata() |
||||
|
.SetPixelWidth(800) |
||||
|
.SetPixelHeight(600), |
||||
|
TagNames = new[] |
||||
|
{ |
||||
|
"tag1", |
||||
|
"tag2" |
||||
|
}.ToHashSet() |
||||
|
}; |
||||
|
|
||||
|
return asset; |
||||
|
} |
||||
|
|
||||
|
public static object Response(IEnrichedAssetEntity asset) |
||||
|
{ |
||||
|
return new |
||||
|
{ |
||||
|
id = asset.Id, |
||||
|
version = asset.Version, |
||||
|
created = asset.Created, |
||||
|
createdBy = asset.CreatedBy.ToString(), |
||||
|
lastModified = asset.LastModified, |
||||
|
lastModifiedBy = asset.LastModifiedBy.ToString(), |
||||
|
url = $"assets/{asset.Id}", |
||||
|
thumbnailUrl = $"assets/{asset.Id}?width=100", |
||||
|
sourceUrl = $"assets/source/{asset.Id}", |
||||
|
mimeType = asset.MimeType, |
||||
|
fileName = asset.FileName, |
||||
|
fileHash = asset.FileHash, |
||||
|
fileSize = asset.FileSize, |
||||
|
fileVersion = asset.FileVersion, |
||||
|
isImage = true, |
||||
|
isProtected = asset.IsProtected, |
||||
|
pixelWidth = asset.Metadata.GetPixelWidth(), |
||||
|
pixelHeight = asset.Metadata.GetPixelHeight(), |
||||
|
tags = asset.TagNames, |
||||
|
type = "IMAGE", |
||||
|
metadataText = asset.MetadataText, |
||||
|
metadataPixelWidth = 800, |
||||
|
metadataUnknown = (string?)null, |
||||
|
metadata = new |
||||
|
{ |
||||
|
pixelWidth = asset.Metadata.GetPixelWidth(), |
||||
|
pixelHeight = asset.Metadata.GetPixelHeight() |
||||
|
}, |
||||
|
slug = asset.Slug, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,305 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL |
||||
|
{ |
||||
|
public static class TestContent |
||||
|
{ |
||||
|
public const string AllFields = @"
|
||||
|
id |
||||
|
version |
||||
|
created |
||||
|
createdBy |
||||
|
lastModified |
||||
|
lastModifiedBy |
||||
|
status |
||||
|
statusColor |
||||
|
url |
||||
|
data { |
||||
|
gql_2Numbers { |
||||
|
iv |
||||
|
} |
||||
|
gql_2Numbers2 { |
||||
|
iv |
||||
|
} |
||||
|
myString { |
||||
|
de |
||||
|
} |
||||
|
myNumber { |
||||
|
iv |
||||
|
} |
||||
|
myNumber2 { |
||||
|
iv |
||||
|
} |
||||
|
myBoolean { |
||||
|
iv |
||||
|
} |
||||
|
myDatetime { |
||||
|
iv |
||||
|
} |
||||
|
myJson { |
||||
|
iv |
||||
|
} |
||||
|
myGeolocation { |
||||
|
iv |
||||
|
} |
||||
|
myTags { |
||||
|
iv |
||||
|
} |
||||
|
myLocalized { |
||||
|
de_DE |
||||
|
} |
||||
|
myArray { |
||||
|
iv { |
||||
|
nestedNumber |
||||
|
nestedNumber2 |
||||
|
nestedBoolean |
||||
|
} |
||||
|
} |
||||
|
}";
|
||||
|
|
||||
|
public static IEnrichedContentEntity Create(NamedId<Guid> schemaId, Guid id, Guid refId, Guid assetId, NamedContentData? data = null) |
||||
|
{ |
||||
|
var now = SystemClock.Instance.GetCurrentInstant(); |
||||
|
|
||||
|
data ??= |
||||
|
new NamedContentData() |
||||
|
.AddField("my-string", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de", "value")) |
||||
|
.AddField("my-assets", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Array(assetId.ToString()))) |
||||
|
.AddField("2_numbers", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 22)) |
||||
|
.AddField("2-numbers", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 23)) |
||||
|
.AddField("my-number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 1.0)) |
||||
|
.AddField("my_number", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", 2.0)) |
||||
|
.AddField("my-boolean", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", true)) |
||||
|
.AddField("my-datetime", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", now)) |
||||
|
.AddField("my-tags", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Array("tag1", "tag2"))) |
||||
|
.AddField("my-references", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Array(refId.ToString()))) |
||||
|
.AddField("my-union", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Array(refId.ToString()))) |
||||
|
.AddField("my-geolocation", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Object().Add("latitude", 10).Add("longitude", 20))) |
||||
|
.AddField("my-json", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Object().Add("value", 1))) |
||||
|
.AddField("my-localized", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("de-DE", "de-DE")) |
||||
|
.AddField("my-array", |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", JsonValue.Array( |
||||
|
JsonValue.Object() |
||||
|
.Add("nested-boolean", true) |
||||
|
.Add("nested-number", 10) |
||||
|
.Add("nested_number", 11), |
||||
|
JsonValue.Object() |
||||
|
.Add("nested-boolean", false) |
||||
|
.Add("nested-number", 20) |
||||
|
.Add("nested_number", 21)))); |
||||
|
|
||||
|
var content = new ContentEntity |
||||
|
{ |
||||
|
Id = id, |
||||
|
Version = 1, |
||||
|
Created = now, |
||||
|
CreatedBy = new RefToken(RefTokenType.Subject, "user1"), |
||||
|
LastModified = now, |
||||
|
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"), |
||||
|
Data = data, |
||||
|
SchemaId = schemaId, |
||||
|
Status = Status.Draft, |
||||
|
StatusColor = "red" |
||||
|
}; |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
public static IEnrichedContentEntity CreateRef(NamedId<Guid> schemaId, Guid id, string field, string value) |
||||
|
{ |
||||
|
var now = SystemClock.Instance.GetCurrentInstant(); |
||||
|
|
||||
|
var data = |
||||
|
new NamedContentData() |
||||
|
.AddField(field, |
||||
|
new ContentFieldData() |
||||
|
.AddValue("iv", value)); |
||||
|
|
||||
|
var content = new ContentEntity |
||||
|
{ |
||||
|
Id = id, |
||||
|
Version = 1, |
||||
|
Created = now, |
||||
|
CreatedBy = new RefToken(RefTokenType.Subject, "user1"), |
||||
|
LastModified = now, |
||||
|
LastModifiedBy = new RefToken(RefTokenType.Subject, "user2"), |
||||
|
Data = data, |
||||
|
SchemaId = schemaId, |
||||
|
Status = Status.Draft, |
||||
|
StatusColor = "red" |
||||
|
}; |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
|
||||
|
public static object Response(IEnrichedContentEntity content) |
||||
|
{ |
||||
|
return new |
||||
|
{ |
||||
|
id = content.Id, |
||||
|
version = 1, |
||||
|
created = content.Created, |
||||
|
createdBy = "subject:user1", |
||||
|
lastModified = content.LastModified, |
||||
|
lastModifiedBy = "subject:user2", |
||||
|
status = "DRAFT", |
||||
|
statusColor = "red", |
||||
|
url = $"contents/my-schema/{content.Id}", |
||||
|
data = Data(content) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public static object Data(IContentEntity content, Guid refId = default, Guid assetId = default) |
||||
|
{ |
||||
|
var result = new Dictionary<string, object> |
||||
|
{ |
||||
|
["gql_2Numbers"] = new |
||||
|
{ |
||||
|
iv = 22 |
||||
|
}, |
||||
|
["gql_2Numbers2"] = new |
||||
|
{ |
||||
|
iv = 23 |
||||
|
}, |
||||
|
["myString"] = new |
||||
|
{ |
||||
|
de = "value" |
||||
|
}, |
||||
|
["myNumber"] = new |
||||
|
{ |
||||
|
iv = 1 |
||||
|
}, |
||||
|
["myNumber2"] = new |
||||
|
{ |
||||
|
iv = 2 |
||||
|
}, |
||||
|
["myBoolean"] = new |
||||
|
{ |
||||
|
iv = true |
||||
|
}, |
||||
|
["myDatetime"] = new |
||||
|
{ |
||||
|
iv = content.LastModified.ToString() |
||||
|
}, |
||||
|
["myJson"] = new |
||||
|
{ |
||||
|
iv = new |
||||
|
{ |
||||
|
value = 1 |
||||
|
} |
||||
|
}, |
||||
|
["myGeolocation"] = new |
||||
|
{ |
||||
|
iv = new |
||||
|
{ |
||||
|
latitude = 10, |
||||
|
longitude = 20 |
||||
|
} |
||||
|
}, |
||||
|
["myTags"] = new |
||||
|
{ |
||||
|
iv = new[] |
||||
|
{ |
||||
|
"tag1", |
||||
|
"tag2" |
||||
|
} |
||||
|
}, |
||||
|
["myLocalized"] = new |
||||
|
{ |
||||
|
de_DE = "de-DE" |
||||
|
}, |
||||
|
["myArray"] = new |
||||
|
{ |
||||
|
iv = new[] |
||||
|
{ |
||||
|
new |
||||
|
{ |
||||
|
nestedNumber = 10, |
||||
|
nestedNumber2 = 11, |
||||
|
nestedBoolean = true |
||||
|
}, |
||||
|
new |
||||
|
{ |
||||
|
nestedNumber = 20, |
||||
|
nestedNumber2 = 21, |
||||
|
nestedBoolean = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
if (refId != default) |
||||
|
{ |
||||
|
result["myReferences"] = new |
||||
|
{ |
||||
|
iv = new[] |
||||
|
{ |
||||
|
refId |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
result["myUnion"] = new |
||||
|
{ |
||||
|
iv = new[] |
||||
|
{ |
||||
|
refId |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (assetId != default) |
||||
|
{ |
||||
|
result["myAssets"] = new |
||||
|
{ |
||||
|
iv = new[] |
||||
|
{ |
||||
|
assetId |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,344 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 = AllTypes.NonNullFloat |
||||
|
}); |
||||
|
|
||||
|
AddField(new FieldType |
||||
|
{ |
||||
|
Name = "longitude", |
||||
|
ResolvedType = AllTypes.NonNullFloat |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using GraphQL.Types; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types |
||||
|
{ |
||||
|
public static class InputFieldExtensions |
||||
|
{ |
||||
|
public static IGraphType GetInputGraphType(this IField field) |
||||
|
{ |
||||
|
return field.Accept(InputFieldVisitor.Default); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// ==========================================================================
|
||||
|
// 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."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue