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" |
"baseUrl": "https://squidex.io" |
||||
} |
} |
||||
} |
} |
||||
Loading…
Reference in new issue