Browse Source

#639 Create JSON schemas for all rule events

pull/645/head
Sebastian 5 years ago
parent
commit
2e4e22c71e
  1. 3
      backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs
  2. 3
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs
  3. 13
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs
  4. 7
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs
  5. 64
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs
  6. 24
      backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs
  7. 47
      backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  8. 3
      backend/src/Squidex/Config/Domain/RuleServices.cs
  9. 70
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/EventJsonSchemaGeneratorTests.cs

3
backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs

@ -34,9 +34,10 @@ namespace Squidex.Extensions.Actions.AzureQueue
protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(EnrichedEvent @event, AzureQueueAction action) protected override async Task<(string Description, AzureQueueJob Data)> CreateJobAsync(EnrichedEvent @event, AzureQueueAction action)
{ {
var requestBody = string.Empty;
var queueName = await FormatAsync(action.Queue, @event); var queueName = await FormatAsync(action.Queue, @event);
string requestBody;
if (!string.IsNullOrEmpty(action.Payload)) if (!string.IsNullOrEmpty(action.Payload))
{ {
requestBody = await FormatAsync(action.Payload, @event); requestBody = await FormatAsync(action.Payload, @event);

3
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs

@ -30,6 +30,7 @@ namespace Squidex.Extensions.Actions.Webhook
protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(EnrichedEvent @event, WebhookAction action) protected override async Task<(string Description, WebhookJob Data)> CreateJobAsync(EnrichedEvent @event, WebhookAction action)
{ {
var requestUrl = await FormatAsync(action.Url, @event);
var requestBody = string.Empty; var requestBody = string.Empty;
var requestSignature = string.Empty; var requestSignature = string.Empty;
@ -47,8 +48,6 @@ namespace Squidex.Extensions.Actions.Webhook
requestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64(); requestSignature = $"{requestBody}{action.SharedSecret}".Sha256Base64();
} }
var requestUrl = await FormatAsync(action.Url, @event);
var ruleDescription = $"Send event to webhook '{requestUrl}'"; var ruleDescription = $"Send event to webhook '{requestUrl}'";
var ruleJob = new WebhookJob var ruleJob = new WebhookJob
{ {

13
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs

@ -9,6 +9,8 @@ using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable CA1822 // Mark members as static
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public sealed class EnrichedCommentEvent : EnrichedUserEventBase public sealed class EnrichedCommentEvent : EnrichedUserEventBase
@ -20,9 +22,20 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
[IgnoreDataMember] [IgnoreDataMember]
public IUser MentionedUser { get; set; } public IUser MentionedUser { get; set; }
[IgnoreDataMember]
public override long Partition public override long Partition
{ {
get { return MentionedUser.Id.GetHashCode(); } get { return MentionedUser.Id.GetHashCode(); }
} }
public bool ShouldSerializeMentionedUser()
{
return false;
}
public bool ShouldSerializePartition()
{
return false;
}
} }
} }

7
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedUserEventBase.cs

@ -9,6 +9,8 @@ using System.Runtime.Serialization;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable CA1822 // Mark members as static
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedUserEventBase : EnrichedEvent public abstract class EnrichedUserEventBase : EnrichedEvent
@ -17,5 +19,10 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
[IgnoreDataMember] [IgnoreDataMember]
public IUser? User { get; set; } public IUser? User { get; set; }
public bool ShouldSerializeUser()
{
return false;
}
} }
} }

64
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs

@ -0,0 +1,64 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using NJsonSchema;
using NJsonSchema.Generation;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.HandleRules
{
public sealed class EventJsonSchemaGenerator
{
private readonly Lazy<Dictionary<string, JsonSchema>> schemas;
private readonly JsonSchemaGenerator schemaGenerator;
public IReadOnlyCollection<string> AllTypes
{
get { return schemas.Value.Keys; }
}
public EventJsonSchemaGenerator(JsonSchemaGenerator schemaGenerator)
{
Guard.NotNull(schemaGenerator, nameof(schemaGenerator));
this.schemaGenerator = schemaGenerator;
schemas = new Lazy<Dictionary<string, JsonSchema>>(GenerateSchemas);
}
public JsonSchema? GetSchema(string typeName)
{
Guard.NotNull(typeName, nameof(typeName));
return schemas.Value.GetOrDefault(typeName);
}
private Dictionary<string, JsonSchema> GenerateSchemas()
{
var result = new Dictionary<string, JsonSchema>(StringComparer.OrdinalIgnoreCase);
var baseType = typeof(EnrichedEvent);
var assembly = baseType.Assembly;
foreach (var type in assembly.GetTypes())
{
if (!type.IsAbstract && type.IsAssignableTo(baseType))
{
var schema = schemaGenerator.Generate(type);
result[type.Name] = schema!;
}
}
return result;
}
}
}

24
backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs

@ -5,9 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using NJsonSchema; using NJsonSchema;
using NJsonSchema.Generation;
using NJsonSchema.Generation.TypeMappers; using NJsonSchema.Generation.TypeMappers;
using NodaTime; using NodaTime;
using NSwag.Generation; using NSwag.Generation;
@ -52,6 +55,18 @@ namespace Squidex.Areas.Api.Config.OpenApi
services.AddSingletonAs<XmlResponseTypesProcessor>() services.AddSingletonAs<XmlResponseTypesProcessor>()
.As<IOperationProcessor>(); .As<IOperationProcessor>();
services.AddSingleton(c =>
{
var settings = ConfigureSchemaSettings(new JsonSchemaGeneratorSettings
{
FlattenInheritanceHierarchy = true,
SerializerOptions = null,
SerializerSettings = c.GetRequiredService<JsonSerializerSettings>()
});
return new JsonSchemaGenerator(settings);
});
services.AddOpenApiDocument(settings => services.AddOpenApiDocument(settings =>
{ {
settings.ConfigureName(); settings.ConfigureName();
@ -68,21 +83,26 @@ namespace Squidex.Areas.Api.Config.OpenApi
settings.Title = "Squidex API"; settings.Title = "Squidex API";
} }
public static void ConfigureSchemaSettings<T>(this T settings) where T : OpenApiDocumentGeneratorSettings public static T ConfigureSchemaSettings<T>(this T settings) where T : JsonSchemaGeneratorSettings
{ {
settings.AllowReferencesWithProperties = true; settings.AllowReferencesWithProperties = true;
settings.TypeMappers = new List<ITypeMapper> settings.TypeMappers = new List<ITypeMapper>
{ {
CreateStringMap<DomainId>(),
CreateStringMap<Instant>(JsonFormatStrings.DateTime), CreateStringMap<Instant>(JsonFormatStrings.DateTime),
CreateStringMap<Language>(), CreateStringMap<Language>(),
CreateStringMap<DomainId>(), CreateStringMap<NamedId<DomainId>>(),
CreateStringMap<NamedId<Guid>>(),
CreateStringMap<NamedId<string>>(),
CreateStringMap<RefToken>(), CreateStringMap<RefToken>(),
CreateStringMap<Status>(), CreateStringMap<Status>(),
CreateObjectMap<JsonObject>(), CreateObjectMap<JsonObject>(),
CreateObjectMap<AssetMetadata>() CreateObjectMap<AssetMetadata>()
}; };
return settings;
} }
private static ITypeMapper CreateObjectMap<T>() private static ITypeMapper CreateObjectMap<T>()

47
backend/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -8,6 +8,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -34,19 +35,22 @@ namespace Squidex.Areas.Api.Controllers.Rules
private readonly IRuleQueryService ruleQuery; private readonly IRuleQueryService ruleQuery;
private readonly IRuleRunnerService ruleRunnerService; private readonly IRuleRunnerService ruleRunnerService;
private readonly IRuleEventRepository ruleEventsRepository; private readonly IRuleEventRepository ruleEventsRepository;
private readonly EventJsonSchemaGenerator eventJsonSchemaGenerator;
private readonly RuleRegistry ruleRegistry; private readonly RuleRegistry ruleRegistry;
public RulesController(ICommandBus commandBus, public RulesController(ICommandBus commandBus,
IRuleEventRepository ruleEventsRepository, IRuleEventRepository ruleEventsRepository,
IRuleQueryService ruleQuery, IRuleQueryService ruleQuery,
IRuleRunnerService ruleRunnerService, IRuleRunnerService ruleRunnerService,
RuleRegistry ruleRegistry) RuleRegistry ruleRegistry,
EventJsonSchemaGenerator eventJsonSchemaGenerator)
: base(commandBus) : base(commandBus)
{ {
this.ruleEventsRepository = ruleEventsRepository; this.ruleEventsRepository = ruleEventsRepository;
this.ruleQuery = ruleQuery; this.ruleQuery = ruleQuery;
this.ruleRunnerService = ruleRunnerService; this.ruleRunnerService = ruleRunnerService;
this.ruleRegistry = ruleRegistry; this.ruleRegistry = ruleRegistry;
this.eventJsonSchemaGenerator = eventJsonSchemaGenerator;
} }
/// <summary> /// <summary>
@ -355,6 +359,47 @@ namespace Squidex.Areas.Api.Controllers.Rules
return NoContent(); return NoContent();
} }
/// <summary>
/// Provide a list of all event types that are used in rules.
/// </summary>
/// <returns>
/// 200 => Rule events returned.
/// </returns>
[HttpGet]
[Route("rules/eventtypes")]
[ProducesResponseType(typeof(List<string>), StatusCodes.Status200OK)]
[AllowAnonymous]
public IActionResult GetEventTypes()
{
var types = eventJsonSchemaGenerator.AllTypes;
return Ok(types);
}
/// <summary>
/// Provide the json schema for the event with the specified name.
/// </summary>
/// <param name="name">The name of the event.</param>
/// <returns>
/// 200 => Rule event type found.
/// 404 => Rule event not found.
/// </returns>
[HttpGet]
[Route("rules/eventtypes/{name}")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[AllowAnonymous]
public IActionResult GetEventSchema(string name)
{
var schema = eventJsonSchemaGenerator.GetSchema(name);
if (schema == null)
{
return NotFound();
}
return Content(schema.ToJson(), "application/json");
}
private async Task<RuleDto> InvokeCommandAsync(ICommand command) private async Task<RuleDto> InvokeCommandAsync(ICommand command)
{ {
var context = await CommandBus.PublishAsync(command); var context = await CommandBus.PublishAsync(command);

3
backend/src/Squidex/Config/Domain/RuleServices.cs

@ -80,6 +80,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<RuleEnqueuer>() services.AddSingletonAs<RuleEnqueuer>()
.As<IRuleEnqueuer>().As<IEventConsumer>(); .As<IRuleEnqueuer>().As<IEventConsumer>();
services.AddSingletonAs<EventJsonSchemaGenerator>()
.AsSelf();
services.AddSingletonAs<RuleRegistry>() services.AddSingletonAs<RuleRegistry>()
.As<ITypeProvider>().AsSelf(); .As<ITypeProvider>().AsSelf();

70
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/EventJsonSchemaGeneratorTests.cs

@ -0,0 +1,70 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using NJsonSchema.Generation;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Xunit;
namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
public class EventJsonSchemaGeneratorTests
{
private readonly EventJsonSchemaGenerator sut;
public EventJsonSchemaGeneratorTests()
{
var jsonSchemaGenerator =
new JsonSchemaGenerator(
new JsonSchemaGeneratorSettings());
sut = new EventJsonSchemaGenerator(jsonSchemaGenerator);
}
public static IEnumerable<string> AllTypes()
{
yield return nameof(EnrichedAssetEvent);
yield return nameof(EnrichedCommentEvent);
yield return nameof(EnrichedContentEvent);
yield return nameof(EnrichedManualEvent);
yield return nameof(EnrichedSchemaEvent);
yield return nameof(EnrichedUsageExceededEvent);
}
public static IEnumerable<object[]> AllTypesData()
{
return AllTypes().Select(x => new object[] { x });
}
[Fact]
public void Should_return_null_for_unknown_type_name()
{
var schema = sut.GetSchema("Unknown");
Assert.Null(schema);
}
[Fact]
public void Should_provide_all_types()
{
var allTypes = sut.AllTypes;
Assert.Equal(AllTypes().ToList(), allTypes);
}
[Theory]
[MemberData(nameof(AllTypesData))]
public void Should_generate_json_schema_for_known_event(string typeName)
{
var schema = sut.GetSchema(typeName);
Assert.NotNull(schema);
}
}
}
Loading…
Cancel
Save