Browse Source

NSwag

pull/336/head
Sebastian Stehle 8 years ago
parent
commit
9035d60063
  1. 6
      src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml
  2. 21
      src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd
  3. 29
      src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs
  4. 10
      src/Squidex.Infrastructure/HashSet.cs
  5. 2
      src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs
  6. 41
      src/Squidex/Areas/Api/Config/Swagger/LogoProcessor.cs
  7. 20
      src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs
  8. 7
      src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs
  9. 13
      src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs
  10. 100
      src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
  11. 3
      src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs
  12. 2
      src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs
  13. 21
      src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs
  14. 6
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs

6
src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<Weavers> <Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Freezable/> <Freezable />
</Weavers> </Weavers>

21
src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuild. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Freezable" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification on the target assembly after all weavers have been finished.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

29
src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs

@ -35,30 +35,35 @@ namespace Squidex.Infrastructure.UsageTracking
new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)), cancellationToken: ct); new CreateIndexModel<MongoUsage>(Index.Ascending(x => x.Key).Ascending(x => x.Category).Ascending(x => x.Date)), cancellationToken: ct);
} }
public async Task TrackUsagesAsync(params UsageUpdate[] updates) public async Task TrackUsagesAsync(UsageUpdate update)
{ {
if (updates.Length == 1) Guard.NotNull(update, nameof(update));
if (update.Counters.Count > 0)
{ {
var value = updates[0]; var (filter, updateStatement) = CreateOperation(update);
if (value.Counters.Count > 0) await Collection.UpdateOneAsync(filter, updateStatement);
{ }
var (filter, update) = CreateOperation(value); }
await Collection.UpdateOneAsync(filter, update, Upsert); public async Task TrackUsagesAsync(params UsageUpdate[] updates)
} {
if (updates.Length == 1)
{
await TrackUsagesAsync(updates[0]);
} }
else if (updates.Length > 0) else if (updates.Length > 0)
{ {
var writes = new List<WriteModel<MongoUsage>>(); var writes = new List<WriteModel<MongoUsage>>();
foreach (var value in updates) foreach (var update in updates)
{ {
if (value.Counters.Count > 0) if (update.Counters.Count > 0)
{ {
var (filter, update) = CreateOperation(value); var (filter, updateStatement) = CreateOperation(update);
writes.Add(new UpdateOneModel<MongoUsage>(filter, update) { IsUpsert = true }); writes.Add(new UpdateOneModel<MongoUsage>(filter, updateStatement) { IsUpsert = true });
} }
} }

10
src/Squidex.Infrastructure/HashSet.cs

@ -15,5 +15,15 @@ namespace Squidex.Infrastructure
{ {
return new HashSet<T>(items); return new HashSet<T>(items);
} }
public static HashSet<T> Of<T>(T item1)
{
return new HashSet<T> { item1 };
}
public static HashSet<T> Of<T>(T item1, T item2)
{
return new HashSet<T> { item1, item2 };
}
} }
} }

2
src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs

@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.UsageTracking
{ {
public interface IUsageRepository public interface IUsageRepository
{ {
Task TrackUsagesAsync(UsageUpdate update);
Task TrackUsagesAsync(params UsageUpdate[] updates); Task TrackUsagesAsync(params UsageUpdate[] updates);
Task<IReadOnlyList<StoredUsage>> QueryAsync(string key, DateTime fromDate, DateTime toDate); Task<IReadOnlyList<StoredUsage>> QueryAsync(string key, DateTime fromDate, DateTime toDate);

41
src/Squidex/Areas/Api/Config/Swagger/LogoProcessor.cs

@ -0,0 +1,41 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Config;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Areas.Api.Config.Swagger
{
public class LogoProcessor : IDocumentProcessor
{
private const string Background = "#3f83df";
private readonly string logo;
public LogoProcessor(IOptions<MyUrlsOptions> urlOptions)
{
logo = urlOptions.Value.BuildUrl("images/logo-white.png", false);
}
public Task ProcessAsync(DocumentProcessorContext context)
{
context.Document.BasePath = Constants.ApiPrefix;
context.Document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = logo, backgroundColor = Background }
};
return TaskHelper.Done;
}
}
}

20
src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs

@ -16,30 +16,32 @@ namespace Squidex.Areas.Api.Config.Swagger
{ {
public sealed class ODataQueryParamsProcessor : IOperationProcessor public sealed class ODataQueryParamsProcessor : IOperationProcessor
{ {
private readonly string path; private readonly string supportedPath;
private readonly string entity; private readonly string entity;
private readonly bool supportSearch; private readonly bool supportSearch;
public ODataQueryParamsProcessor(string path, string entity, bool supportSearch) public ODataQueryParamsProcessor(string supportedPath, string entity, bool supportSearch)
{ {
this.path = path;
this.entity = entity; this.entity = entity;
this.supportSearch = supportSearch; this.supportSearch = supportSearch;
this.supportedPath = supportedPath;
} }
public Task<bool> ProcessAsync(OperationProcessorContext context) public Task<bool> ProcessAsync(OperationProcessorContext context)
{ {
if (context.OperationDescription.Path == path) if (context.OperationDescription.Path == supportedPath)
{ {
var operation = context.OperationDescription.Operation;
if (supportSearch) if (supportSearch)
{ {
context.OperationDescription.Operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search."); operation.AddQueryParameter("$search", JsonObjectType.String, "Optional OData full text search.");
} }
context.OperationDescription.Operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take."); operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take.");
context.OperationDescription.Operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip."); operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip.");
context.OperationDescription.Operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition."); operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition.");
context.OperationDescription.Operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition."); operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition.");
} }
return TaskHelper.True; return TaskHelper.True;

7
src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs

@ -18,7 +18,7 @@ using Squidex.Infrastructure.Tasks;
namespace Squidex.Areas.Api.Config.Swagger namespace Squidex.Areas.Api.Config.Swagger
{ {
public class ScopesProcessor : IOperationProcessor public sealed class ScopesProcessor : IOperationProcessor
{ {
public Task<bool> ProcessAsync(OperationProcessorContext context) public Task<bool> ProcessAsync(OperationProcessorContext context)
{ {
@ -28,8 +28,9 @@ namespace Squidex.Areas.Api.Config.Swagger
} }
var authorizeAttributes = var authorizeAttributes =
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Union( context.MethodInfo.GetCustomAttributes<AuthorizeAttribute>(true).Union(
context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<AuthorizeAttribute>()).ToArray(); context.MethodInfo.DeclaringType.GetCustomAttributes<AuthorizeAttribute>(true))
.ToArray();
if (authorizeAttributes.Any()) if (authorizeAttributes.Any())
{ {

13
src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs

@ -6,9 +6,6 @@
// ========================================================================== // ==========================================================================
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NSwag.AspNetCore;
using Squidex.Config; using Squidex.Config;
namespace Squidex.Areas.Api.Config.Swagger namespace Squidex.Areas.Api.Config.Swagger
@ -17,15 +14,9 @@ namespace Squidex.Areas.Api.Config.Swagger
{ {
public static void UseMySwagger(this IApplicationBuilder app) public static void UseMySwagger(this IApplicationBuilder app)
{ {
var urlOptions = app.ApplicationServices.GetService<IOptions<MyUrlsOptions>>().Value; app.UseSwagger(settings =>
app.UseSwaggerWithApiExplorer(settings =>
{ {
settings.AddAssetODataParams(); settings.Path = $"{Constants.ApiPrefix}/swagger/v1/swagger.json";
settings.ConfigureNames();
settings.ConfigurePaths(urlOptions);
settings.ConfigureSchemaSettings();
settings.ConfigureIdentity(urlOptions);
}); });
} }
} }

100
src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs

@ -13,6 +13,7 @@ using NJsonSchema.Generation.TypeMappers;
using NodaTime; using NodaTime;
using NSwag.AspNetCore; using NSwag.AspNetCore;
using NSwag.SwaggerGeneration; using NSwag.SwaggerGeneration;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Security; using NSwag.SwaggerGeneration.Processors.Security;
using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.Contents.Generator;
using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Areas.Api.Controllers.Rules.Models;
@ -26,73 +27,72 @@ namespace Squidex.Areas.Api.Config.Swagger
{ {
public static void AddMySwaggerSettings(this IServiceCollection services) public static void AddMySwaggerSettings(this IServiceCollection services)
{ {
services.AddSingleton(typeof(SwaggerSettings<SwaggerGeneratorSettings>), s => services.AddSingletonAs<RuleActionProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<XmlTagProcessor>()
.As<IDocumentProcessor>();
services.AddSingletonAs<TagByGroupNameProcessor>()
.As<IOperationProcessor>();
services.AddSingletonAs<XmlResponseTypesProcessor>()
.As<IOperationProcessor>();
services.AddSingleton(c =>
{ {
var urlOptions = s.GetService<IOptions<MyUrlsOptions>>().Value; var settings = new SwaggerDocumentSettings { SchemaType = SchemaType.OpenApi3 };
return new SwaggerDocumentRegistration(settings.DocumentName, generator);
}))
var settings = new SwaggerSettings<SwaggerGeneratorSettings>() settings.DocumentProcessors.Add(new RuleActionProcessor());
.AddAssetODataParams() settings.DocumentProcessors.Add(new XmlTagProcessor());
.ConfigureNames()
.ConfigurePaths(urlOptions)
.ConfigureSchemaSettings()
.ConfigureIdentity(urlOptions);
return settings; settings.OperationProcessors.Add(new TagByGroupNameProcessor());
settings.OperationProcessors.Add(new XmlResponseTypesProcessor());
services.AddOpenApiDocument(configure =>
{
var urlOptions = configure.GetService<IOptions<MyUrlsOptions>>().Value;
configure.AddAssetODataParams();
configure.ConfigureNames();
configure.ConfigurePaths(urlOptions);
configure.ConfigureSchemaSettings();
configure.ConfigureIdentity(urlOptions);
}); });
services.AddTransient<SchemasSwaggerGenerator>(); services.AddTransient<SchemasSwaggerGenerator>();
} }
public static SwaggerSettings<T> ConfigureNames<T>(this SwaggerSettings<T> settings) where T : SwaggerGeneratorSettings, new() public static void AddAssetODataParams<T>(this T settings) where T : SwaggerGeneratorSettings
{ {
settings.GeneratorSettings.Title = "Squidex API"; settings.OperationProcessors.Add(new ODataQueryParamsProcessor("/apps/{app}/assets", "assets", false));
settings.GeneratorSettings.Version = "1.0";
return settings;
} }
public static SwaggerSettings<T> AddAssetODataParams<T>(this SwaggerSettings<T> settings) where T : SwaggerGeneratorSettings, new() public static void ConfigureNames<T>(this T settings) where T : SwaggerGeneratorSettings
{ {
settings.GeneratorSettings.OperationProcessors.Add(new ODataQueryParamsProcessor("/apps/{app}/assets", "assets", false)); settings.Title = "Squidex API";
return settings;
} }
public static SwaggerSettings<T> ConfigureIdentity<T>(this SwaggerSettings<T> settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings, new() public static void ConfigureIdentity<T>(this T settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings
{ {
settings.GeneratorSettings.DocumentProcessors.Add( settings.DocumentProcessors.Add(
new SecurityDefinitionAppender( new SecurityDefinitionAppender(
Constants.SecurityDefinition, SwaggerHelper.CreateOAuthSchema(urlOptions))); Constants.SecurityDefinition, SwaggerHelper.CreateOAuthSchema(urlOptions)));
settings.GeneratorSettings.OperationProcessors.Add(new ScopesProcessor()); settings.OperationProcessors.Add(new ScopesProcessor());
return settings;
} }
public static SwaggerSettings<T> ConfigurePaths<T>(this SwaggerSettings<T> settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings, new() public static void ConfigureSchemaSettings<T>(this T settings) where T : SwaggerGeneratorSettings
{ {
settings.SwaggerRoute = $"{Constants.ApiPrefix}/swagger/v1/swagger.json"; settings.DefaultEnumHandling = EnumHandling.String;
settings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
settings.PostProcess = document => settings.TypeMappers = new List<ITypeMapper>
{
document.BasePath = Constants.ApiPrefix;
document.Info.ExtensionData = new Dictionary<string, object>
{
["x-logo"] = new { url = urlOptions.BuildUrl("images/logo-white.png", false), backgroundColor = "#3f83df" }
};
};
settings.MiddlewareBasePath = Constants.ApiPrefix;
return settings;
}
public static SwaggerSettings<T> ConfigureSchemaSettings<T>(this SwaggerSettings<T> settings) where T : SwaggerGeneratorSettings, new()
{
settings.GeneratorSettings.DefaultEnumHandling = EnumHandling.String;
settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase;
settings.GeneratorSettings.TypeMappers = new List<ITypeMapper>
{ {
new PrimitiveTypeMapper(typeof(Instant), schema => new PrimitiveTypeMapper(typeof(Instant), schema =>
{ {
@ -103,13 +103,11 @@ namespace Squidex.Areas.Api.Config.Swagger
new PrimitiveTypeMapper(typeof(RefToken), s => s.Type = JsonObjectType.String) new PrimitiveTypeMapper(typeof(RefToken), s => s.Type = JsonObjectType.String)
}; };
settings.GeneratorSettings.DocumentProcessors.Add(new RuleActionProcessor()); settings.DocumentProcessors.Add(new RuleActionProcessor());
settings.GeneratorSettings.DocumentProcessors.Add(new XmlTagProcessor()); settings.DocumentProcessors.Add(new XmlTagProcessor());
settings.GeneratorSettings.OperationProcessors.Add(new TagByGroupNameProcessor());
settings.GeneratorSettings.OperationProcessors.Add(new XmlResponseTypesProcessor());
return settings; settings.OperationProcessors.Add(new TagByGroupNameProcessor());
settings.OperationProcessors.Add(new XmlResponseTypesProcessor());
} }
} }
} }

3
src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs

@ -70,8 +70,7 @@ namespace Squidex.Areas.Api.Config.Swagger
private static void RemoveOkResponse(SwaggerOperation operation) private static void RemoveOkResponse(SwaggerOperation operation)
{ {
if (operation.Responses.TryGetValue("200", out var response) && if (operation.Responses.TryGetValue("200", out var response) && response.Description?.Contains("=>") == true)
response.Description?.Contains("=>") == true)
{ {
operation.Responses.Remove("200"); operation.Responses.Remove("200");
} }

2
src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs

@ -47,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
var schemas = await appProvider.GetSchemasAsync(AppId); var schemas = await appProvider.GetSchemasAsync(AppId);
var swaggerDocument = await schemasSwaggerGenerator.Generate(App, schemas); var swaggerDocument = await schemasSwaggerGenerator.Generate(HttpContext, App, schemas);
return Content(swaggerDocument.ToJson(), "application/json"); return Content(swaggerDocument.ToJson(), "application/json");
} }

21
src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs

@ -14,6 +14,7 @@ using NJsonSchema;
using NSwag; using NSwag;
using NSwag.AspNetCore; using NSwag.AspNetCore;
using NSwag.SwaggerGeneration; using NSwag.SwaggerGeneration;
using Squidex.Areas.Api.Config.Swagger;
using Squidex.Config; using Squidex.Config;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
@ -24,26 +25,26 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator
{ {
public sealed class SchemasSwaggerGenerator public sealed class SchemasSwaggerGenerator
{ {
private readonly HttpContext context;
private readonly SwaggerSettings<SwaggerGeneratorSettings> settings;
private readonly MyUrlsOptions urlOptions; private readonly MyUrlsOptions urlOptions;
private readonly SwaggerDocumentSettings settings = new SwaggerDocumentSettings();
private SwaggerJsonSchemaGenerator schemaGenerator; private SwaggerJsonSchemaGenerator schemaGenerator;
private JsonSchemaResolver schemaResolver;
private SwaggerDocument document; private SwaggerDocument document;
private JsonSchemaResolver schemaResolver;
public SchemasSwaggerGenerator(IHttpContextAccessor context, SwaggerSettings<SwaggerGeneratorSettings> settings, IOptions<MyUrlsOptions> urlOptions) public SchemasSwaggerGenerator(IOptions<MyUrlsOptions> urlOptions)
{ {
this.context = context.HttpContext;
this.settings = settings;
this.urlOptions = urlOptions.Value; this.urlOptions = urlOptions.Value;
settings.ConfigureNames();
settings.Conf
} }
public async Task<SwaggerDocument> Generate(IAppEntity app, IEnumerable<ISchemaEntity> schemas) public async Task<SwaggerDocument> Generate(HttpContext httpContext, IAppEntity app, IEnumerable<ISchemaEntity> schemas)
{ {
document = SwaggerHelper.CreateApiDocument(context, urlOptions, app.Name); document = SwaggerHelper.CreateApiDocument(httpContext, urlOptions, app.Name);
schemaGenerator = new SwaggerJsonSchemaGenerator(settings.GeneratorSettings); schemaGenerator = new SwaggerJsonSchemaGenerator(settings);
schemaResolver = new SwaggerSchemaResolver(document, settings.GeneratorSettings); schemaResolver = new SwaggerSchemaResolver(document, settings);
GenerateSchemasOperations(schemas, app); GenerateSchemasOperations(schemas, app);

6
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs

@ -25,15 +25,13 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
{ {
var discriminator = new OpenApiDiscriminator var discriminator = new OpenApiDiscriminator
{ {
JsonInheritanceConverter = new JsonInheritanceConverter("actionType", typeof(RuleAction)), JsonInheritanceConverter = new JsonInheritanceConverter("actionType", typeof(RuleAction)), PropertyName = "actionType"
PropertyName = "actionType"
}; };
schema.DiscriminatorObject = discriminator; schema.DiscriminatorObject = discriminator;
schema.Properties["actionType"] = new JsonProperty schema.Properties["actionType"] = new JsonProperty
{ {
Type = JsonObjectType.String, Type = JsonObjectType.String, IsRequired = true
IsRequired = true
}; };
foreach (var derived in RuleElementRegistry.Actions) foreach (var derived in RuleElementRegistry.Actions)

Loading…
Cancel
Save