Skip to content

Commit

Permalink
Schema name. (#1032)
Browse files Browse the repository at this point in the history
* Schema name.

* Fix build.
  • Loading branch information
SebastianStehle authored Oct 9, 2023
1 parent 9ab6dbd commit 16bf2ba
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 43 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@
<data name="EventType" xml:space="preserve">
<value>The type of the event.</value>
</data>
<data name="GraphqlRequest" xml:space="preserve">
<value>The graphql request.</value>
</data>
<data name="ItemData" xml:space="preserve">
<value>The current item, if the field is part of an array.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static class ContentHeaders
public const string KeyNoResolveLanguages = "X-NoResolveLanguages";
public const string KeyResolveFlow = "X-ResolveFlow";
public const string KeyResolveUrls = "X-ResolveUrls";
public const string KeyResolveSchemaNames = "X-ResolveSchemaName";
public const string KeyUnpublished = "X-Unpublished";

public static void AddCacheHeaders(this Context context, IRequestCache cache)
Expand Down Expand Up @@ -98,6 +99,16 @@ public static ICloneBuilder WithResolveFlow(this ICloneBuilder builder, bool val
return builder.WithBoolean(KeyResolveFlow, value);
}

public static bool ResolveSchemaNames(this Context context)
{
return context.AsBoolean(KeyResolveSchemaNames);
}

public static ICloneBuilder WithResolveSchemaNames(this ICloneBuilder builder, bool value = true)
{
return builder.WithBoolean(KeyResolveSchemaNames, value);
}

public static bool NoResolveLanguages(this Context context)
{
return context.AsBoolean(KeyNoResolveLanguages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public GraphQLExecutionContext(
this.dataLoaders = dataLoaders;

Context = context.Clone(b => b
.WithResolveSchemaNames()
.WithNoCleanup()
.WithNoEnrichment()
.WithNoAssetEnrichment());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
// ==========================================================================

using GraphQL.Types;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using static Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents.ContentActions;

namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;

Expand All @@ -28,34 +30,62 @@ public ApplicationQueries(Builder builder, IEnumerable<SchemaInfo> schemaInfos)
continue;
}

AddContentFind(schemaInfo, contentType);
AddContentQueries(builder, schemaInfo, contentType);
if (schemaInfo.Schema.SchemaDef.Type == SchemaType.Singleton)
{
// Mark the normal queries as deprecated to motivate using the new endpoint.
var deprecation = $"Use 'find{schemaInfo.TypeName}Singleton' instead.";

AddContentFind(schemaInfo, contentType, deprecation);
AddContentFindSingleton(schemaInfo, contentType);
AddContentQueries(builder, schemaInfo, contentType, deprecation);
}
else
{
AddContentFind(schemaInfo, contentType, null);
AddContentQueries(builder, schemaInfo, contentType, null);
}
}

Description = "The app queries.";
}

private void AddContentFind(SchemaInfo schemaInfo, IGraphType contentType)
private void AddContentFind(SchemaInfo schemaInfo, IGraphType contentType, string? deprecatedReason)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"find{schemaInfo.TypeName}Content",
Arguments = ContentActions.Find.Arguments,
Arguments = Find.Arguments,
ResolvedType = contentType,
Resolver = ContentActions.Find.Resolver,
Resolver = Find.Resolver,
DeprecationReason = deprecatedReason,
Description = $"Find an {schemaInfo.DisplayName} content by id.",
SchemaId = schemaInfo.Schema.Id
});
}

private void AddContentQueries(Builder builder, SchemaInfo schemaInfo, IGraphType contentType)
private void AddContentFindSingleton(SchemaInfo schemaInfo, IGraphType contentType)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"find{schemaInfo.TypeName}Singleton",
Arguments = FindSingleton.Arguments,
ResolvedType = contentType,
Resolver = FindSingleton.Resolver,
DeprecationReason = null,
Description = $"Find an {schemaInfo.DisplayName} singleton.",
SchemaId = schemaInfo.Schema.Id
});
}

private void AddContentQueries(Builder builder, SchemaInfo schemaInfo, IGraphType contentType, string? deprecatedReason)
{
AddField(new FieldTypeWithSchemaId
{
Name = $"query{schemaInfo.TypeName}Contents",
Arguments = ContentActions.QueryOrReferencing.Arguments,
Arguments = QueryOrReferencing.Arguments,
ResolvedType = new ListGraphType(new NonNullGraphType(contentType)),
Resolver = ContentActions.QueryOrReferencing.Query,
Resolver = QueryOrReferencing.Query,
DeprecationReason = deprecatedReason,
Description = $"Query {schemaInfo.DisplayName} content items.",
SchemaId = schemaInfo.Schema.Id
});
Expand All @@ -70,9 +100,10 @@ private void AddContentQueries(Builder builder, SchemaInfo schemaInfo, IGraphTyp
AddField(new FieldTypeWithSchemaId
{
Name = $"query{schemaInfo.TypeName}ContentsWithTotal",
Arguments = ContentActions.QueryOrReferencing.Arguments,
Arguments = QueryOrReferencing.Arguments,
ResolvedType = contentResultTyp,
Resolver = ContentActions.QueryOrReferencing.QueryWithTotal,
Resolver = QueryOrReferencing.QueryWithTotal,
DeprecationReason = deprecatedReason,
Description = $"Query {schemaInfo.DisplayName} content items with total count.",
SchemaId = schemaInfo.Schema.Id
});
Expand All @@ -90,9 +121,9 @@ private void AddContentQuery(Builder builder)
AddField(new FieldType
{
Name = "queryContentsByIds",
Arguments = ContentActions.QueryByIds.Arguments,
Arguments = QueryByIds.Arguments,
ResolvedType = new NonNullGraphType(new ListGraphType(new NonNullGraphType(unionType))),
Resolver = ContentActions.QueryByIds.Resolver,
Resolver = QueryByIds.Resolver,
Description = "Query content items by IDs across schemeas."
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,36 @@ public static class Find
});
}

public static class FindSingleton
{
public static readonly QueryArguments Arguments = new QueryArguments
{
new QueryArgument(Scalars.Int)
{
Name = "version",
Description = FieldDescriptions.QueryVersion,
DefaultValue = null
}
};

public static readonly IFieldResolver Resolver = Resolvers.Sync<object, object?>((_, fieldContext, context) =>
{
var contentSchemaId = fieldContext.FieldDefinition.SchemaId();
var contentVersion = fieldContext.GetArgument<int?>("version");

if (contentVersion >= 0)
{
return context.GetContent(contentSchemaId, contentSchemaId, contentVersion.Value);
}
else
{
return context.GetContent(contentSchemaId, contentSchemaId,
fieldContext.FieldNames(),
fieldContext.CacheDuration());
}
});
}

public static class QueryByIds
{
public static readonly QueryArguments Arguments = new QueryArguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ private ContentConverter GenerateConverter(Context context, ResolvedComponents c
{
converter.Add(new ResolveAssetUrls(context.App.NamedId(), urlGenerator, assetUrls));
}
}

if (!context.IsFrontendClient || context.ResolveSchemaNames())
{
converter.Add(new AddSchemaNames(components));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using NJsonSchema;
using NSwag;
using NSwag.Annotations;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Domain.Apps.Core;

namespace Squidex.Areas.Api.Config.OpenApi;

public sealed class AcceptAnyBodyAttribute : OpenApiOperationProcessorAttribute
{
public AcceptAnyBodyAttribute()
: base(typeof(Processor))
{
}

public sealed class Processor : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.Parameters.Add(
new OpenApiParameter
{
Name = "request",
Kind = OpenApiParameterKind.Body,
Schema = new JsonSchema
{
},
Description = FieldDescriptions.GraphqlRequest
});

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,13 @@ public AcceptQueryAttribute(bool supportsSearch)
{
}

public sealed class Processor : IOperationProcessor
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
public sealed record Processor(bool SupportsSearch) : IOperationProcessor
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
{
private readonly bool supportsSearch;

public Processor(bool supportsSearch)
{
this.supportsSearch = supportsSearch;
}

public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.AddQuery(supportsSearch);
context.OperationDescription.Operation.AddQuery(SupportsSearch);
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
namespace Squidex.Areas.Api.Controllers.Contents;

[SchemaMustBePublished]
[ApiExplorerSettings(GroupName = nameof(Contents))]
public sealed class ContentsSharedController : ApiController
{
private readonly IContentQueryService contentQuery;
Expand All @@ -39,18 +40,97 @@ public ContentsSharedController(ICommandBus commandBus,
/// GraphQL endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The request parameters.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[Route("content/{app}/graphql/")]
[Route("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult GetGraphQL(string app)
public IActionResult GetGraphQL(string app, GraphQLQueryDto request)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};

return new GraphQLExecutionActionResult<DummySchema>(options);
}

/// <summary>
/// GraphQL endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpPost("content/{app}/graphql/")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptAnyBody]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult PostGraphQL(string app)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};

return new GraphQLExecutionActionResult<DummySchema>(options);
}

/// <summary>
/// GraphQL batch endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="request">The request object.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpGet("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult GetGraphQLBatch(string app, GraphQLQueryDto request)
{
var options = new GraphQLHttpMiddlewareOptions
{
DefaultResponseContentType = new MediaTypeHeaderValue("application/json")
};

return new GraphQLExecutionActionResult<DummySchema>(options);
}

/// <summary>
/// GraphQL batch endpoint.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <response code="200">Contents returned or mutated.</response>.
/// <response code="404">App not found.</response>.
/// <remarks>
/// You can read the generated documentation for your app at /api/content/{appName}/docs.
/// </remarks>
[HttpPost("content/{app}/graphql/batch")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous]
[ApiCosts(2)]
[AcceptAnyBody]
[AcceptHeader.Unpublished]
[IgnoreCacheFilter]
public IActionResult PostGraphQLBatch(string app)
{
var options = new GraphQLHttpMiddlewareOptions
{
Expand Down Expand Up @@ -143,7 +223,7 @@ public async Task<IActionResult> GetAllContentsPost(string app, [FromBody] AllCo
[ProducesResponseType(typeof(BulkResultDto[]), StatusCodes.Status200OK)]
[ApiPermissionOrAnonymous(PermissionIds.AppContentsReadOwn)]
[ApiCosts(5)]
public async Task<IActionResult> BulkUpdateContents(string app, string schema, [FromBody] BulkUpdateContentsDto request)
public async Task<IActionResult> BulkUpdateAllContents(string app, string schema, [FromBody] BulkUpdateContentsDto request)
{
var command = request.ToCommand(true);

Expand Down
Loading

0 comments on commit 16bf2ba

Please sign in to comment.