mirror of https://github.com/Squidex/squidex.git
32 changed files with 525 additions and 55 deletions
@ -0,0 +1,51 @@ |
|||
// ==========================================================================
|
|||
// SwaggerIdentityUsage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using NSwag; |
|||
using NSwag.AspNetCore; |
|||
using NSwag.CodeGeneration.SwaggerGenerators.WebApi.Processors.Security; |
|||
|
|||
namespace Squidex.Configurations.Identity |
|||
{ |
|||
public static class SwaggerIdentityUsage |
|||
{ |
|||
private const string DescriptionPattern = |
|||
@"To retrieve an access token, the client id must make a request to the token url. For example:
|
|||
|
|||
$ curl |
|||
-X POST '{0}' |
|||
-H 'Content-Type: application/x-www-form-urlencoded' |
|||
-d 'grant_type=client_credentials&client_id=[APP_NAME]&client_secret=[CLIENT_KEY]'";
|
|||
|
|||
public static SwaggerOwinSettings ConfigureIdentity(this SwaggerOwinSettings settings, MyUrlsOptions options) |
|||
{ |
|||
var tokenUrl = options.BuildUrl($"{Constants.IdentityPrefix}/connect/token"); |
|||
|
|||
var description = string.Format(CultureInfo.InvariantCulture, DescriptionPattern, tokenUrl); |
|||
|
|||
settings.DocumentProcessors.Add( |
|||
new SecurityDefinitionAppender("OAuth2", new SwaggerSecurityScheme |
|||
{ |
|||
TokenUrl = tokenUrl, |
|||
Type = SwaggerSecuritySchemeType.OAuth2, |
|||
Flow = SwaggerOAuth2Flow.Application, |
|||
Scopes = new Dictionary<string, string> |
|||
{ |
|||
{ Constants.ApiScope, "Read and write access to the API" } |
|||
}, |
|||
Description = description |
|||
})); |
|||
|
|||
settings.OperationProcessors.Add(new OperationSecurityScopeProcessor("roles")); |
|||
|
|||
return settings; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// MyUrlsOptions.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Configurations |
|||
{ |
|||
public sealed class MyUrlsOptions |
|||
{ |
|||
public string BaseUrl { get; set; } |
|||
|
|||
public string BuildUrl(string path, bool trailingSlash = true) |
|||
{ |
|||
var url = $"{BaseUrl.TrimEnd('/')}/{path.Trim('/')}"; |
|||
|
|||
if (trailingSlash && |
|||
url.IndexOf("?", StringComparison.OrdinalIgnoreCase) < 0 && |
|||
url.IndexOf(";", StringComparison.OrdinalIgnoreCase) < 0) |
|||
{ |
|||
url = url + "/"; |
|||
} |
|||
|
|||
return url; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
// ==========================================================================
|
|||
// SwaggerUsage.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using NJsonSchema; |
|||
using NJsonSchema.Generation.TypeMappers; |
|||
using NSwag; |
|||
using NSwag.AspNetCore; |
|||
using NSwag.CodeGeneration.SwaggerGenerators.WebApi; |
|||
using NSwag.CodeGeneration.SwaggerGenerators.WebApi.Processors; |
|||
using NSwag.CodeGeneration.SwaggerGenerators.WebApi.Processors.Contexts; |
|||
using Squidex.Configurations.Identity; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Modules.Api; |
|||
using Squidex.Pipeline; |
|||
|
|||
namespace Squidex.Configurations.Web |
|||
{ |
|||
public static class SwaggerUsage |
|||
{ |
|||
public sealed class DescriptionResponseTypeAttributeProcessor : IOperationProcessor |
|||
{ |
|||
private readonly WebApiToSwaggerGeneratorSettings settings; |
|||
|
|||
public DescriptionResponseTypeAttributeProcessor(WebApiToSwaggerGeneratorSettings settings) |
|||
{ |
|||
this.settings = settings; |
|||
} |
|||
|
|||
public bool Process(OperationProcessorContext context) |
|||
{ |
|||
context.OperationDescription.Operation.Responses.Remove("200"); |
|||
|
|||
var responseTypes = |
|||
context.MethodInfo.GetCustomAttributes<DescribedResponseTypeAttribute>().ToList(); |
|||
|
|||
responseTypes.Add(new DescribedResponseTypeAttribute(500, typeof(ErrorDto), "Operation failed.")); |
|||
|
|||
foreach (var attribute in responseTypes) |
|||
{ |
|||
var responseType = attribute.Type; |
|||
|
|||
var typeDescription = |
|||
JsonObjectTypeDescription.FromType(responseType, |
|||
context.MethodInfo.ReturnParameter?.GetCustomAttributes(), settings.DefaultEnumHandling); |
|||
|
|||
var responseCode = attribute.StatusCode.ToString(CultureInfo.InvariantCulture); |
|||
var response = new SwaggerResponse { Description = attribute.Description }; |
|||
|
|||
if (IsVoidResponse(responseType) == false) |
|||
{ |
|||
response.IsNullableRaw = typeDescription.IsNullable; |
|||
response.Schema = context.SwaggerGenerator.GenerateAndAppendSchemaFromType(responseType, typeDescription.IsNullable, null); |
|||
} |
|||
|
|||
context.OperationDescription.Operation.Responses[responseCode] = response; |
|||
|
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private static bool IsVoidResponse(Type returnType) |
|||
{ |
|||
return returnType == null || returnType == typeof(void); |
|||
} |
|||
} |
|||
|
|||
public static void UseMySwagger(this IApplicationBuilder app) |
|||
{ |
|||
var options = app.ApplicationServices.GetService<IOptions<MyUrlsOptions>>().Value; |
|||
|
|||
var settings = |
|||
new SwaggerOwinSettings { Title = "Squidex API Specification" } |
|||
.ConfigurePaths() |
|||
.ConfigureSchemaSettings() |
|||
.ConfigureIdentity(options); |
|||
|
|||
app.UseSwagger(typeof(SwaggerUsage).GetTypeInfo().Assembly, settings); |
|||
} |
|||
|
|||
private static SwaggerOwinSettings ConfigurePaths(this SwaggerOwinSettings settings) |
|||
{ |
|||
settings.SwaggerRoute = $"{Constants.ApiPrefix}/swagger/v1/swagger.json"; |
|||
|
|||
settings.PostProcess = document => |
|||
{ |
|||
document.BasePath = Constants.ApiPrefix; |
|||
}; |
|||
|
|||
settings.MiddlewareBasePath = Constants.ApiPrefix; |
|||
|
|||
return settings; |
|||
} |
|||
|
|||
private static SwaggerOwinSettings ConfigureSchemaSettings(this SwaggerOwinSettings settings) |
|||
{ |
|||
settings.DefaultEnumHandling = EnumHandling.String; |
|||
settings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase; |
|||
|
|||
settings.TypeMappers = new List<ITypeMapper> |
|||
{ |
|||
new PrimitiveTypeMapper(typeof(Language), s => s.Type = JsonObjectType.String) |
|||
}; |
|||
|
|||
settings.OperationProcessors.Add(new DescriptionResponseTypeAttributeProcessor(settings)); |
|||
|
|||
return settings; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// ==========================================================================
|
|||
// DocsController.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Mvc; |
|||
using NSwag.Annotations; |
|||
|
|||
namespace Squidex.Modules.Api.Docs |
|||
{ |
|||
[SwaggerIgnore] |
|||
public sealed class DocsController : Controller |
|||
{ |
|||
[HttpGet] |
|||
[Route("docs/")] |
|||
public IActionResult Docs() |
|||
{ |
|||
return View(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// DescribedResponseTypeAttribute.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
|
|||
namespace Squidex.Pipeline |
|||
{ |
|||
public sealed class DescribedResponseTypeAttribute : ProducesResponseTypeAttribute |
|||
{ |
|||
public string Description { get; } |
|||
|
|||
public DescribedResponseTypeAttribute(int statusCode, string description = null) |
|||
: base(typeof(void), statusCode) |
|||
{ |
|||
Description = description; |
|||
} |
|||
|
|||
public DescribedResponseTypeAttribute(int statusCode, Type type, string description = null) |
|||
: base(type, statusCode) |
|||
{ |
|||
Description = description; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>API Docs</title> |
|||
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
</head> |
|||
<body> |
|||
<redoc spec-url="@Url.Content("~/swagger/v1/swagger.json")"></redoc> |
|||
|
|||
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"></script> |
|||
</body> |
|||
</html> |
|||
@ -1,5 +1,5 @@ |
|||
{ |
|||
"identity": { |
|||
"urls": { |
|||
"baseUrl": "https://squidex.io" |
|||
} |
|||
} |
|||
Loading…
Reference in new issue