From 9035d60063706f7e9b98ebf1bd91de9fe43013d5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 20 Nov 2018 19:26:47 +0100 Subject: [PATCH] NSwag --- .../FodyWeavers.xml | 6 +- .../FodyWeavers.xsd | 21 ++++ .../UsageTracking/MongoUsageRepository.cs | 29 ++--- src/Squidex.Infrastructure/HashSet.cs | 10 ++ .../UsageTracking/IUsageRepository.cs | 2 + .../Areas/Api/Config/Swagger/LogoProcessor.cs | 41 +++++++ .../Swagger/ODataQueryParamsProcessor.cs | 20 ++-- .../Api/Config/Swagger/ScopesProcessor.cs | 7 +- .../Api/Config/Swagger/SwaggerExtensions.cs | 13 +-- .../Api/Config/Swagger/SwaggerServices.cs | 100 +++++++++--------- .../Swagger/XmlResponseTypesProcessor.cs | 3 +- .../Contents/ContentSwaggerController.cs | 2 +- .../Generator/SchemasSwaggerGenerator.cs | 21 ++-- .../Rules/Models/RuleActionProcessor.cs | 6 +- 14 files changed, 175 insertions(+), 106 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd create mode 100644 src/Squidex/Areas/Api/Config/Swagger/LogoProcessor.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml b/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml index 0444e1d26..e3c32ed72 100644 --- a/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml +++ b/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xml @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd b/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd new file mode 100644 index 000000000..216cf10fd --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/FodyWeavers.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + 'true' to run assembly verification on the target assembly after all weavers have been finished. + + + + + A comma separated list of error codes that can be safely ignored in assembly verification. + + + + + \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs b/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs index 57ad2dd2e..b748e4311 100644 --- a/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs +++ b/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs @@ -35,30 +35,35 @@ namespace Squidex.Infrastructure.UsageTracking new CreateIndexModel(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) - { - var (filter, update) = CreateOperation(value); + await Collection.UpdateOneAsync(filter, updateStatement); + } + } - 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) { var writes = new List>(); - 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(filter, update) { IsUpsert = true }); + writes.Add(new UpdateOneModel(filter, updateStatement) { IsUpsert = true }); } } diff --git a/src/Squidex.Infrastructure/HashSet.cs b/src/Squidex.Infrastructure/HashSet.cs index 697d2bb39..c536e28b1 100644 --- a/src/Squidex.Infrastructure/HashSet.cs +++ b/src/Squidex.Infrastructure/HashSet.cs @@ -15,5 +15,15 @@ namespace Squidex.Infrastructure { return new HashSet(items); } + + public static HashSet Of(T item1) + { + return new HashSet { item1 }; + } + + public static HashSet Of(T item1, T item2) + { + return new HashSet { item1, item2 }; + } } } diff --git a/src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs b/src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs index 58a47028d..224a5ab4d 100644 --- a/src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs +++ b/src/Squidex.Infrastructure/UsageTracking/IUsageRepository.cs @@ -13,6 +13,8 @@ namespace Squidex.Infrastructure.UsageTracking { public interface IUsageRepository { + Task TrackUsagesAsync(UsageUpdate update); + Task TrackUsagesAsync(params UsageUpdate[] updates); Task> QueryAsync(string key, DateTime fromDate, DateTime toDate); diff --git a/src/Squidex/Areas/Api/Config/Swagger/LogoProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/LogoProcessor.cs new file mode 100644 index 000000000..5e5bae0f8 --- /dev/null +++ b/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 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 + { + ["x-logo"] = new { url = logo, backgroundColor = Background } + }; + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs index 7a4ba6e8e..2e5deca78 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/ODataQueryParamsProcessor.cs @@ -16,30 +16,32 @@ namespace Squidex.Areas.Api.Config.Swagger { public sealed class ODataQueryParamsProcessor : IOperationProcessor { - private readonly string path; + private readonly string supportedPath; private readonly string entity; 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.supportSearch = supportSearch; + this.supportedPath = supportedPath; } public Task ProcessAsync(OperationProcessorContext context) { - if (context.OperationDescription.Path == path) + if (context.OperationDescription.Path == supportedPath) { + var operation = context.OperationDescription.Operation; + 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."); - context.OperationDescription.Operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip."); - context.OperationDescription.Operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition."); - context.OperationDescription.Operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition."); + operation.AddQueryParameter("$top", JsonObjectType.Number, $"Optional number of {entity} to take."); + operation.AddQueryParameter("$skip", JsonObjectType.Number, $"Optional number of {entity} to skip."); + operation.AddQueryParameter("$orderby", JsonObjectType.String, "Optional OData order definition."); + operation.AddQueryParameter("$filter", JsonObjectType.String, "Optional OData filter definition."); } return TaskHelper.True; diff --git a/src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs index 3b8fca2ae..6ae4cffea 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/ScopesProcessor.cs @@ -18,7 +18,7 @@ using Squidex.Infrastructure.Tasks; namespace Squidex.Areas.Api.Config.Swagger { - public class ScopesProcessor : IOperationProcessor + public sealed class ScopesProcessor : IOperationProcessor { public Task ProcessAsync(OperationProcessorContext context) { @@ -28,8 +28,9 @@ namespace Squidex.Areas.Api.Config.Swagger } var authorizeAttributes = - context.MethodInfo.GetCustomAttributes(true).OfType().Union( - context.MethodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType()).ToArray(); + context.MethodInfo.GetCustomAttributes(true).Union( + context.MethodInfo.DeclaringType.GetCustomAttributes(true)) + .ToArray(); if (authorizeAttributes.Any()) { diff --git a/src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs b/src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs index 07e0f2c6f..6c1e4ca13 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs @@ -6,9 +6,6 @@ // ========================================================================== using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using NSwag.AspNetCore; using Squidex.Config; namespace Squidex.Areas.Api.Config.Swagger @@ -17,15 +14,9 @@ namespace Squidex.Areas.Api.Config.Swagger { public static void UseMySwagger(this IApplicationBuilder app) { - var urlOptions = app.ApplicationServices.GetService>().Value; - - app.UseSwaggerWithApiExplorer(settings => + app.UseSwagger(settings => { - settings.AddAssetODataParams(); - settings.ConfigureNames(); - settings.ConfigurePaths(urlOptions); - settings.ConfigureSchemaSettings(); - settings.ConfigureIdentity(urlOptions); + settings.Path = $"{Constants.ApiPrefix}/swagger/v1/swagger.json"; }); } } diff --git a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs index 336025ee0..87a8c4076 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs @@ -13,6 +13,7 @@ using NJsonSchema.Generation.TypeMappers; using NodaTime; using NSwag.AspNetCore; using NSwag.SwaggerGeneration; +using NSwag.SwaggerGeneration.Processors; using NSwag.SwaggerGeneration.Processors.Security; using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.Rules.Models; @@ -26,73 +27,72 @@ namespace Squidex.Areas.Api.Config.Swagger { public static void AddMySwaggerSettings(this IServiceCollection services) { - services.AddSingleton(typeof(SwaggerSettings), s => + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingleton(c => { - var urlOptions = s.GetService>().Value; + var settings = new SwaggerDocumentSettings { SchemaType = SchemaType.OpenApi3 }; + + return new SwaggerDocumentRegistration(settings.DocumentName, generator); + })) + + - var settings = new SwaggerSettings() - .AddAssetODataParams() - .ConfigureNames() - .ConfigurePaths(urlOptions) - .ConfigureSchemaSettings() - .ConfigureIdentity(urlOptions); + settings.DocumentProcessors.Add(new RuleActionProcessor()); + settings.DocumentProcessors.Add(new XmlTagProcessor()); - return settings; + settings.OperationProcessors.Add(new TagByGroupNameProcessor()); + settings.OperationProcessors.Add(new XmlResponseTypesProcessor()); + + services.AddOpenApiDocument(configure => + { + var urlOptions = configure.GetService>().Value; + + configure.AddAssetODataParams(); + configure.ConfigureNames(); + configure.ConfigurePaths(urlOptions); + configure.ConfigureSchemaSettings(); + configure.ConfigureIdentity(urlOptions); }); services.AddTransient(); } - public static SwaggerSettings ConfigureNames(this SwaggerSettings settings) where T : SwaggerGeneratorSettings, new() + public static void AddAssetODataParams(this T settings) where T : SwaggerGeneratorSettings { - settings.GeneratorSettings.Title = "Squidex API"; - settings.GeneratorSettings.Version = "1.0"; - - return settings; + settings.OperationProcessors.Add(new ODataQueryParamsProcessor("/apps/{app}/assets", "assets", false)); } - public static SwaggerSettings AddAssetODataParams(this SwaggerSettings settings) where T : SwaggerGeneratorSettings, new() + public static void ConfigureNames(this T settings) where T : SwaggerGeneratorSettings { - settings.GeneratorSettings.OperationProcessors.Add(new ODataQueryParamsProcessor("/apps/{app}/assets", "assets", false)); - - return settings; + settings.Title = "Squidex API"; } - public static SwaggerSettings ConfigureIdentity(this SwaggerSettings settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings, new() + public static void ConfigureIdentity(this T settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings { - settings.GeneratorSettings.DocumentProcessors.Add( + settings.DocumentProcessors.Add( new SecurityDefinitionAppender( Constants.SecurityDefinition, SwaggerHelper.CreateOAuthSchema(urlOptions))); - settings.GeneratorSettings.OperationProcessors.Add(new ScopesProcessor()); - - return settings; + settings.OperationProcessors.Add(new ScopesProcessor()); } - public static SwaggerSettings ConfigurePaths(this SwaggerSettings settings, MyUrlsOptions urlOptions) where T : SwaggerGeneratorSettings, new() + public static void ConfigureSchemaSettings(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 => - { - document.BasePath = Constants.ApiPrefix; - document.Info.ExtensionData = new Dictionary - { - ["x-logo"] = new { url = urlOptions.BuildUrl("images/logo-white.png", false), backgroundColor = "#3f83df" } - }; - }; - - settings.MiddlewareBasePath = Constants.ApiPrefix; - - return settings; - } - - public static SwaggerSettings ConfigureSchemaSettings(this SwaggerSettings settings) where T : SwaggerGeneratorSettings, new() - { - settings.GeneratorSettings.DefaultEnumHandling = EnumHandling.String; - settings.GeneratorSettings.DefaultPropertyNameHandling = PropertyNameHandling.CamelCase; - - settings.GeneratorSettings.TypeMappers = new List + settings.TypeMappers = new List { new PrimitiveTypeMapper(typeof(Instant), schema => { @@ -103,13 +103,11 @@ namespace Squidex.Areas.Api.Config.Swagger new PrimitiveTypeMapper(typeof(RefToken), s => s.Type = JsonObjectType.String) }; - settings.GeneratorSettings.DocumentProcessors.Add(new RuleActionProcessor()); - settings.GeneratorSettings.DocumentProcessors.Add(new XmlTagProcessor()); - - settings.GeneratorSettings.OperationProcessors.Add(new TagByGroupNameProcessor()); - settings.GeneratorSettings.OperationProcessors.Add(new XmlResponseTypesProcessor()); + settings.DocumentProcessors.Add(new RuleActionProcessor()); + settings.DocumentProcessors.Add(new XmlTagProcessor()); - return settings; + settings.OperationProcessors.Add(new TagByGroupNameProcessor()); + settings.OperationProcessors.Add(new XmlResponseTypesProcessor()); } } } diff --git a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs index ebab2b885..d968607fb 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs @@ -70,8 +70,7 @@ namespace Squidex.Areas.Api.Config.Swagger private static void RemoveOkResponse(SwaggerOperation operation) { - if (operation.Responses.TryGetValue("200", out var response) && - response.Description?.Contains("=>") == true) + if (operation.Responses.TryGetValue("200", out var response) && response.Description?.Contains("=>") == true) { operation.Responses.Remove("200"); } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs index 8d4c01e96..aaf2aadf3 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentSwaggerController.cs +++ b/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 swaggerDocument = await schemasSwaggerGenerator.Generate(App, schemas); + var swaggerDocument = await schemasSwaggerGenerator.Generate(HttpContext, App, schemas); return Content(swaggerDocument.ToJson(), "application/json"); } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs index 03d7120bd..f955d8afd 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemasSwaggerGenerator.cs @@ -14,6 +14,7 @@ using NJsonSchema; using NSwag; using NSwag.AspNetCore; using NSwag.SwaggerGeneration; +using Squidex.Areas.Api.Config.Swagger; using Squidex.Config; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Schemas; @@ -24,26 +25,26 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator { public sealed class SchemasSwaggerGenerator { - private readonly HttpContext context; - private readonly SwaggerSettings settings; private readonly MyUrlsOptions urlOptions; + private readonly SwaggerDocumentSettings settings = new SwaggerDocumentSettings(); private SwaggerJsonSchemaGenerator schemaGenerator; - private JsonSchemaResolver schemaResolver; private SwaggerDocument document; + private JsonSchemaResolver schemaResolver; - public SchemasSwaggerGenerator(IHttpContextAccessor context, SwaggerSettings settings, IOptions urlOptions) + public SchemasSwaggerGenerator(IOptions urlOptions) { - this.context = context.HttpContext; - this.settings = settings; this.urlOptions = urlOptions.Value; + + settings.ConfigureNames(); + settings.Conf } - public async Task Generate(IAppEntity app, IEnumerable schemas) + public async Task Generate(HttpContext httpContext, IAppEntity app, IEnumerable schemas) { - document = SwaggerHelper.CreateApiDocument(context, urlOptions, app.Name); + document = SwaggerHelper.CreateApiDocument(httpContext, urlOptions, app.Name); - schemaGenerator = new SwaggerJsonSchemaGenerator(settings.GeneratorSettings); - schemaResolver = new SwaggerSchemaResolver(document, settings.GeneratorSettings); + schemaGenerator = new SwaggerJsonSchemaGenerator(settings); + schemaResolver = new SwaggerSchemaResolver(document, settings); GenerateSchemasOperations(schemas, app); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs index c2a93b912..e5eba534b 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs @@ -25,15 +25,13 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models { var discriminator = new OpenApiDiscriminator { - JsonInheritanceConverter = new JsonInheritanceConverter("actionType", typeof(RuleAction)), - PropertyName = "actionType" + JsonInheritanceConverter = new JsonInheritanceConverter("actionType", typeof(RuleAction)), PropertyName = "actionType" }; schema.DiscriminatorObject = discriminator; schema.Properties["actionType"] = new JsonProperty { - Type = JsonObjectType.String, - IsRequired = true + Type = JsonObjectType.String, IsRequired = true }; foreach (var derived in RuleElementRegistry.Actions)