Browse Source

Merge branch 'master' of github.com:Squidex/squidex

pull/400/head
Sebastian Stehle 7 years ago
parent
commit
579c06e515
  1. 41
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  2. 11
      src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs
  3. 50
      src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs
  4. 10
      src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs
  5. 2
      src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs
  6. 5
      src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  7. 7
      src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs
  8. 37
      src/Squidex/Areas/Api/Config/OpenApi/ThemeProcessor.cs
  9. 31
      src/Squidex/Areas/Api/Config/OpenApi/VersionProcessor.cs
  10. 104
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs
  11. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  12. 2
      src/Squidex/Docs/schemabody.md
  13. 6
      src/Squidex/Docs/security.md
  14. 21
      src/Squidex/Pipeline/OpenApi/NSwagHelper.cs
  15. 58
      tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs

41
src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Entities.Apps
{
public sealed class AppCommandMiddleware : GrainCommandMiddleware<AppCommand, IAppGrain>
{
private readonly IContextProvider contextProvider;
public AppCommandMiddleware(IGrainFactory grainFactory, IContextProvider contextProvider)
: base(grainFactory)
{
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contextProvider = contextProvider;
}
public override async Task HandleAsync(CommandContext context, Func<Task> next)
{
await ExecuteCommandAsync(context);
if (context.PlainResult is IAppEntity app)
{
contextProvider.Context.App = app;
}
await next();
}
}
}

11
src/Squidex.Infrastructure/Commands/GrainCommandMiddleware.cs

@ -23,6 +23,13 @@ namespace Squidex.Infrastructure.Commands
} }
public virtual async Task HandleAsync(CommandContext context, Func<Task> next) public virtual async Task HandleAsync(CommandContext context, Func<Task> next)
{
await ExecuteCommandAsync(context);
await next();
}
protected async Task ExecuteCommandAsync(CommandContext context)
{ {
if (context.Command is TCommand typedCommand) if (context.Command is TCommand typedCommand)
{ {
@ -30,11 +37,9 @@ namespace Squidex.Infrastructure.Commands
context.Complete(result); context.Complete(result);
} }
await next();
} }
protected async Task<object> ExecuteCommandAsync(TCommand typedCommand) private async Task<object> ExecuteCommandAsync(TCommand typedCommand)
{ {
var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId); var grain = grainFactory.GetGrain<TGrain>(typedCommand.AggregateId);

50
src/Squidex/Areas/Api/Config/OpenApi/CommonProcessor.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using NSwag;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class CommonProcessor : IDocumentProcessor
{
private readonly string version;
private readonly string backgroundColor = "#3f83df";
private readonly string logoUrl;
private readonly OpenApiExternalDocumentation documentation = new OpenApiExternalDocumentation
{
Url = "https://docs.squidex.io"
};
public CommonProcessor(ExposedValues exposedValues, IOptions<UrlsOptions> urlOptions)
{
logoUrl = urlOptions.Value.BuildUrl("images/logo-white.png", false);
if (!exposedValues.TryGetValue("version", out version))
{
version = "1.0";
}
}
public void Process(DocumentProcessorContext context)
{
context.Document.BasePath = Constants.ApiPrefix;
context.Document.Info.Version = version;
context.Document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = logoUrl, backgroundColor }
};
context.Document.ExternalDocumentation = documentation;
}
}
}

10
src/Squidex/Areas/Api/Config/OpenApi/ODataExtensions.cs

@ -17,13 +17,13 @@ namespace Squidex.Areas.Api.Config.OpenApi
{ {
if (supportSearch) if (supportSearch)
{ {
operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search."); operation.AddQuery("$search", JsonObjectType.String, "Optional OData full text search.");
} }
operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take."); operation.AddQuery("$top", JsonObjectType.Number, $"Optional number of {entity} to take.");
operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip."); operation.AddQuery("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip.");
operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition."); operation.AddQuery("$orderby", JsonObjectType.String, "Optional OData order definition.");
operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition."); operation.AddQuery("$filter", JsonObjectType.String, "Optional OData filter definition.");
} }
} }
} }

2
src/Squidex/Areas/Api/Config/OpenApi/ODataQueryParamsProcessor.cs

@ -5,10 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using NJsonSchema;
using NSwag.Generation.Processors; using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts; using NSwag.Generation.Processors.Contexts;
using Squidex.Pipeline.OpenApi;
namespace Squidex.Areas.Api.Config.OpenApi namespace Squidex.Areas.Api.Config.OpenApi
{ {

5
src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs

@ -29,10 +29,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
services.AddSingletonAs<RuleActionProcessor>() services.AddSingletonAs<RuleActionProcessor>()
.As<IDocumentProcessor>(); .As<IDocumentProcessor>();
services.AddSingletonAs<ThemeProcessor>() services.AddSingletonAs<CommonProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<VersionProcessor>()
.As<IDocumentProcessor>(); .As<IDocumentProcessor>();
services.AddSingletonAs<XmlTagProcessor>() services.AddSingletonAs<XmlTagProcessor>()

7
src/Squidex/Areas/Api/Config/OpenApi/SecurityProcessor.cs

@ -35,7 +35,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
SetupDescription(security, tokenUrl); SetupDescription(security, tokenUrl);
SetupFlow(security); SetupFlow(security);
SetupScropes(security); SetupScopes(security);
return security; return security;
} }
@ -45,7 +45,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
security.Flow = OpenApiOAuth2Flow.Application; security.Flow = OpenApiOAuth2Flow.Application;
} }
private static void SetupScropes(OpenApiSecurityScheme security) private static void SetupScopes(OpenApiSecurityScheme security)
{ {
security.Scopes = new Dictionary<string, string> security.Scopes = new Dictionary<string, string>
{ {
@ -55,8 +55,7 @@ namespace Squidex.Areas.Api.Config.OpenApi
private static void SetupDescription(OpenApiSecurityScheme securityScheme, string tokenUrl) private static void SetupDescription(OpenApiSecurityScheme securityScheme, string tokenUrl)
{ {
var securityDocs = NSwagHelper.LoadDocs("security"); var securityText = NSwagHelper.SecurityDocs.Replace("<TOKEN_URL>", tokenUrl);
var securityText = securityDocs.Replace("<TOKEN_URL>", tokenUrl);
securityScheme.Description = securityText; securityScheme.Description = securityText;
} }

37
src/Squidex/Areas/Api/Config/OpenApi/ThemeProcessor.cs

@ -1,37 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Microsoft.Extensions.Options;
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class ThemeProcessor : IDocumentProcessor
{
private const string Background = "#3f83df";
private readonly string url;
public ThemeProcessor(IOptions<UrlsOptions> urlOptions)
{
url = urlOptions.Value.BuildUrl("images/logo-white.png", false);
}
public void Process(DocumentProcessorContext context)
{
context.Document.BasePath = Constants.ApiPrefix;
context.Document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url, backgroundColor = Background }
};
}
}
}

31
src/Squidex/Areas/Api/Config/OpenApi/VersionProcessor.cs

@ -1,31 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using NSwag.Generation.Processors;
using NSwag.Generation.Processors.Contexts;
using Squidex.Web;
namespace Squidex.Areas.Api.Config.OpenApi
{
public sealed class VersionProcessor : IDocumentProcessor
{
private readonly ExposedValues exposedValues;
public VersionProcessor(ExposedValues exposedValues)
{
this.exposedValues = exposedValues;
}
public void Process(DocumentProcessorContext context)
{
if (exposedValues.TryGetValue("version", out var version))
{
context.Document.Info.Version = version;
}
}
}
}

104
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaOpenApiGenerator.cs

@ -23,8 +23,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
public sealed class SchemaOpenApiGenerator public sealed class SchemaOpenApiGenerator
{ {
private static readonly string SchemaQueryDescription;
private static readonly string SchemaBodyDescription;
private readonly ContentSchemaBuilder schemaBuilder = new ContentSchemaBuilder(); private readonly ContentSchemaBuilder schemaBuilder = new ContentSchemaBuilder();
private readonly OpenApiDocument document; private readonly OpenApiDocument document;
private readonly JsonSchema contentSchema; private readonly JsonSchema contentSchema;
@ -36,12 +34,6 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private readonly JsonSchema statusSchema; private readonly JsonSchema statusSchema;
private readonly string appName; private readonly string appName;
static SchemaOpenApiGenerator()
{
SchemaBodyDescription = NSwagHelper.LoadDocs("schemabody");
SchemaQueryDescription = NSwagHelper.LoadDocs("schemaquery");
}
public SchemaOpenApiGenerator( public SchemaOpenApiGenerator(
OpenApiDocument document, OpenApiDocument document,
string appName, string appName,
@ -72,10 +64,10 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
document.Tags.Add( document.Tags.Add(
new OpenApiTag new OpenApiTag
{ {
Name = schemaName, Description = $"API to managed {schemaName} contents." Name = schemaName, Description = $"API to manage {schemaName} contents."
}); });
var schemaOperations = new List<OpenApiPathItem> var schemaOperations = new[]
{ {
GenerateSchemaGetsOperation(), GenerateSchemaGetsOperation(),
GenerateSchemaGetOperation(), GenerateSchemaGetOperation(),
@ -95,109 +87,103 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
private OpenApiPathItem GenerateSchemaGetsOperation() private OpenApiPathItem GenerateSchemaGetsOperation()
{ {
return AddOperation(OpenApiOperationMethod.Get, null, $"{appPath}/{schemaPath}", operation => return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/",
operation =>
{ {
operation.OperationId = $"Query{schemaType}Contents"; operation.OperationId = $"Query{schemaType}Contents";
operation.Summary = $"Queries {schemaName} contents."; operation.Summary = $"Queries {schemaName} contents.";
operation.Description = SchemaQueryDescription; operation.Description = NSwagHelper.SchemaQueryDocs;
operation.AddOData("contents", true); operation.AddOData("contents", true);
operation.AddResponse("200", $"{schemaName} contents retrieved.", CreateContentsSchema(schemaName, contentSchema)); operation.AddResponse("200", $"{schemaName} contents retrieved.", CreateContentsSchema(schemaName, contentSchema));
operation.AddResponse("400", $"{schemaName} query not valid."); operation.AddResponse("400", $"{schemaName} query not valid.");
AddSecurity(operation, Permissions.AppContentsRead);
}); });
} }
private OpenApiPathItem GenerateSchemaGetOperation() private OpenApiPathItem GenerateSchemaGetOperation()
{ {
return AddOperation(OpenApiOperationMethod.Get, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => return Add(OpenApiOperationMethod.Get, Permissions.AppContentsRead, "/{id}", operation =>
{ {
operation.OperationId = $"Get{schemaType}Content"; operation.OperationId = $"Get{schemaType}Content";
operation.Summary = $"Get a {schemaName} content."; operation.Summary = $"Get a {schemaName} content.";
operation.AddResponse("200", $"{schemaName} content found.", contentSchema); operation.AddResponse("200", $"{schemaName} content found.", contentSchema);
AddSecurity(operation, Permissions.AppContentsRead);
}); });
} }
private OpenApiPathItem GenerateSchemaCreateOperation() private OpenApiPathItem GenerateSchemaCreateOperation()
{ {
return AddOperation(OpenApiOperationMethod.Post, null, $"{appPath}/{schemaPath}", operation => return Add(OpenApiOperationMethod.Post, Permissions.AppContentsCreate, "/",
operation =>
{ {
operation.OperationId = $"Create{schemaType}Content"; operation.OperationId = $"Create{schemaType}Content";
operation.Summary = $"Create a {schemaName} content."; operation.Summary = $"Create a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription); operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddQueryParameter("publish", JsonObjectType.Boolean, "Set to true to autopublish content."); operation.AddQuery("publish", JsonObjectType.Boolean, "Set to true to autopublish content.");
operation.AddResponse("201", $"{schemaName} content created.", contentSchema); operation.AddResponse("201", $"{schemaName} content created.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid."); operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsCreate);
}); });
} }
private OpenApiPathItem GenerateSchemaUpdateOperation() private OpenApiPathItem GenerateSchemaUpdateOperation()
{ {
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}",
operation =>
{ {
operation.OperationId = $"Update{schemaType}Content"; operation.OperationId = $"Update{schemaType}Content";
operation.Summary = $"Update a {schemaName} content."; operation.Summary = $"Update a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription); operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddResponse("200", $"{schemaName} content updated.", contentSchema); operation.AddResponse("200", $"{schemaName} content updated.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid."); operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
}); });
} }
private OpenApiPathItem GenerateSchemaUpdatePatchOperation() private OpenApiPathItem GenerateSchemaUpdatePatchOperation()
{ {
return AddOperation(OpenApiOperationMethod.Patch, schemaName, $"{appPath}/{schemaPath}/{{id}}", operation => return Add(OpenApiOperationMethod.Patch, Permissions.AppContentsUpdate, "/{id}",
operation =>
{ {
operation.OperationId = $"Path{schemaType}Content"; operation.OperationId = $"Path{schemaType}Content";
operation.Summary = $"Patch a {schemaName} content."; operation.Summary = $"Patch a {schemaName} content.";
operation.AddBodyParameter("data", dataSchema, SchemaBodyDescription); operation.AddBody("data", dataSchema, NSwagHelper.SchemaBodyDocs);
operation.AddResponse("200", $"{schemaName} content patched.", contentSchema); operation.AddResponse("200", $"{schemaName} content patched.", contentSchema);
operation.AddResponse("400", $"{schemaName} status not valid."); operation.AddResponse("400", $"{schemaName} status not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
}); });
} }
private OpenApiPathItem GenerateSchemaStatusOperation() private OpenApiPathItem GenerateSchemaStatusOperation()
{ {
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/status", operation => return Add(OpenApiOperationMethod.Put, Permissions.AppContentsUpdate, "/{id}/status",
operation =>
{ {
operation.OperationId = $"Change{schemaType}ContentStatus"; operation.OperationId = $"Change{schemaType}ContentStatus";
operation.Summary = $"Change status of {schemaName} content."; operation.Summary = $"Change status of {schemaName} content.";
operation.AddBodyParameter("request", statusSchema, "The request to change content status."); operation.AddBody("request", statusSchema, "The request to change content status.");
operation.AddResponse("204", $"{schemaName} content status changed.", contentSchema); operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("400", $"{schemaName} content not valid."); operation.AddResponse("400", $"{schemaName} content not valid.");
AddSecurity(operation, Permissions.AppContentsUpdate);
}); });
} }
private OpenApiPathItem GenerateSchemaDiscardOperation() private OpenApiPathItem GenerateSchemaDiscardOperation()
{ {
return AddOperation(OpenApiOperationMethod.Put, schemaName, $"{appPath}/{schemaPath}/{{id}}/discard", operation => return Add(OpenApiOperationMethod.Put, Permissions.AppContentsDraftDiscard, "/{id}/discard",
operation =>
{ {
operation.OperationId = $"Discard{schemaType}Content"; operation.OperationId = $"Discard{schemaType}Content";
@ -205,39 +191,48 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema); operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema);
operation.AddResponse("400", $"{schemaName} content has no pending draft."); operation.AddResponse("400", $"{schemaName} content has no pending draft.");
AddSecurity(operation, Permissions.AppContentsDraftDiscard);
}); });
} }
private OpenApiPathItem GenerateSchemaDeleteOperation() private OpenApiPathItem GenerateSchemaDeleteOperation()
{ {
return AddOperation(OpenApiOperationMethod.Delete, schemaName, $"{appPath}/{schemaPath}/{{id}}/", operation => return Add(OpenApiOperationMethod.Delete, Permissions.AppContentsDelete, "/{id}",
operation =>
{ {
operation.OperationId = $"Delete{schemaType}Content"; operation.OperationId = $"Delete{schemaType}Content";
operation.Summary = $"Delete a {schemaName} content."; operation.Summary = $"Delete a {schemaName} content.";
operation.AddResponse("204", $"{schemaName} content deleted."); operation.AddResponse("204", $"{schemaName} content deleted.");
AddSecurity(operation, Permissions.AppContentsDelete);
}); });
} }
private OpenApiPathItem AddOperation(string method, string entityName, string path, Action<OpenApiOperation> updater) private OpenApiPathItem Add(string method, string permission, string path, Action<OpenApiOperation> updater)
{ {
var operations = document.Paths.GetOrAddNew(path); var operations = document.Paths.GetOrAddNew($"{appPath}/{schemaPath}{path}");
var operation = new OpenApiOperation(); var operation = new OpenApiOperation
{
Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[Constants.SecurityDefinition] = new[]
{
Permissions.ForApp(permission, appName, schemaPath).Id
}
}
}
};
updater(operation); updater(operation);
operations[method] = operation; operations[method] = operation;
if (entityName != null) if (path.StartsWith("/{id}", StringComparison.OrdinalIgnoreCase))
{ {
operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {entityName} content (GUID)."); operation.AddPathParameter("id", JsonObjectType.String, $"The id of the {schemaName} content.", JsonFormatStrings.Guid);
operation.AddResponse("404", $"App, schema or {entityName} content not found."); operation.AddResponse("404", $"App, schema or {schemaName} content not found.");
} }
return operations; return operations;
@ -255,7 +250,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
}, },
["items"] = new JsonSchemaProperty ["items"] = new JsonSchemaProperty
{ {
Type = JsonObjectType.Array, IsRequired = true, Item = contentSchema, Description = $"The {schemaName} contents." Type = JsonObjectType.Array, IsRequired = true, Description = $"The {schemaName} contents.", Item = contentSchema
} }
}, },
Type = JsonObjectType.Object Type = JsonObjectType.Object
@ -263,18 +258,5 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
return schema; return schema;
} }
private void AddSecurity(OpenApiOperation operation, string permission)
{
if (operation.Security == null)
{
operation.Security = new List<OpenApiSecurityRequirement>();
}
operation.Security.Add(new OpenApiSecurityRequirement
{
[Constants.SecurityDefinition] = new[] { Permissions.ForApp(permission, appName, schemaPath).Id }
});
}
} }
} }

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -22,7 +22,6 @@ using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Apps.Invitation; using Squidex.Domain.Apps.Entities.Apps.Invitation;
using Squidex.Domain.Apps.Entities.Apps.Templates; using Squidex.Domain.Apps.Entities.Apps.Templates;
@ -246,7 +245,7 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AppsByNameIndexCommandMiddleware>() services.AddSingletonAs<AppsByNameIndexCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<AppCommand, IAppGrain>>() services.AddSingletonAs<AppCommandMiddleware>()
.As<ICommandMiddleware>(); .As<ICommandMiddleware>();
services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>() services.AddSingletonAs<GrainCommandMiddleware<CommentsCommand, ICommentGrain>>()

2
src/Squidex/Docs/schemabody.md

@ -1,6 +1,6 @@
The data of the content to be created or updated. The data of the content to be created or updated.
Please note that each field is an object with one entry per language. Please note that each field is an object with one entry per language.
If the field is not localizable you must use `iv` (Invariant Language) as a key. If the field is not localizable you must use `iv` (invariant language) as a key.
Read more about it at: https://docs.squidex.io/04-guides/02-api.html Read more about it at: https://docs.squidex.io/04-guides/02-api.html

6
src/Squidex/Docs/security.md

@ -10,4 +10,8 @@ To retrieve an access token, the client id must make a request to the token url.
client_secret=[CLIENT_SECRET]& client_secret=[CLIENT_SECRET]&
scope=squidex-api' scope=squidex-api'
[APP_NAME] is the name of your app. You have to create a client to generate an access token. `[APP_NAME]` is the name of your app. You have to create a client to generate an access token.
You must send this token in the `Authorization` header when making requests to the API:
Authorization: Bearer <token>

21
src/Squidex/Pipeline/OpenApi/NSwagHelper.cs

@ -16,7 +16,13 @@ namespace Squidex.Pipeline.OpenApi
{ {
public static class NSwagHelper public static class NSwagHelper
{ {
public static string LoadDocs(string name) public static readonly string SecurityDocs = LoadDocs("security");
public static readonly string SchemaBodyDocs = LoadDocs("schemabody");
public static readonly string SchemaQueryDocs = LoadDocs("schemaquery");
private static string LoadDocs(string name)
{ {
var assembly = typeof(NSwagHelper).Assembly; var assembly = typeof(NSwagHelper).Assembly;
@ -65,21 +71,21 @@ namespace Squidex.Pipeline.OpenApi
return document; return document;
} }
public static void AddQueryParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description = null) public static void AddQuery(this OpenApiOperation operation, string name, JsonObjectType type, string description)
{ {
var schema = new JsonSchema { Type = type }; var schema = new JsonSchema { Type = type };
operation.AddParameter(name, schema, OpenApiParameterKind.Query, description, false); operation.AddParameter(name, schema, OpenApiParameterKind.Query, description, false);
} }
public static void AddPathParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description = null) public static void AddPathParameter(this OpenApiOperation operation, string name, JsonObjectType type, string description, string format = null)
{ {
var schema = new JsonSchema { Type = type }; var schema = new JsonSchema { Type = type, Format = format };
operation.AddParameter(name, schema, OpenApiParameterKind.Path, description, true); operation.AddParameter(name, schema, OpenApiParameterKind.Path, description, true);
} }
public static void AddBodyParameter(this OpenApiOperation operation, string name, JsonSchema schema, string description) public static void AddBody(this OpenApiOperation operation, string name, JsonSchema schema, string description)
{ {
operation.AddParameter(name, schema, OpenApiParameterKind.Body, description, true); operation.AddParameter(name, schema, OpenApiParameterKind.Body, description, true);
} }
@ -93,10 +99,7 @@ namespace Squidex.Pipeline.OpenApi
parameter.Description = description; parameter.Description = description;
} }
if (isRequired) parameter.IsRequired = isRequired;
{
parameter.IsRequired = true;
}
operation.Parameters.Add(parameter); operation.Parameters.Add(parameter);
} }

58
tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs

@ -0,0 +1,58 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using FakeItEasy;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Infrastructure.Commands;
using Xunit;
namespace Squidex.Domain.Apps.Entities.Apps
{
public class AppCommandMiddlewareTests : HandlerTestBase<AppState>
{
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly Guid appId = Guid.NewGuid();
private readonly Context requestContext = new Context();
private readonly AppCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand
{
}
protected override Guid Id
{
get { return appId; }
}
public AppCommandMiddlewareTests()
{
A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new AppCommandMiddleware(A.Fake<IGrainFactory>(), contextProvider);
}
[Fact]
public async Task Should_replace_context_app_with_grain_result()
{
var result = A.Fake<IAppEntity>();
var command = CreateCommand(new MyCommand());
var context = CreateContextForCommand(command);
context.Complete(result);
await sut.HandleAsync(context);
Assert.Same(result, requestContext.App);
}
}
}
Loading…
Cancel
Save