mirror of https://github.com/Squidex/squidex.git
38 changed files with 749 additions and 410 deletions
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleActionDtoFactory.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Controllers.Api.Rules.Models.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models.Converters |
||||
|
{ |
||||
|
public sealed class RuleActionDtoFactory : IRuleActionVisitor<RuleActionDto> |
||||
|
{ |
||||
|
private static readonly RuleActionDtoFactory Instance = new RuleActionDtoFactory(); |
||||
|
|
||||
|
private RuleActionDtoFactory() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static RuleActionDto Create(RuleAction properties) |
||||
|
{ |
||||
|
return properties.Accept(Instance); |
||||
|
} |
||||
|
|
||||
|
public RuleActionDto Visit(WebhookAction action) |
||||
|
{ |
||||
|
return SimpleMapper.Map(action, new WebhookActionDto()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleConverter.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Read.Rules; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models.Converters |
||||
|
{ |
||||
|
public static class RuleConverter |
||||
|
{ |
||||
|
public static RuleDto ToModel(this IRuleEntity entity) |
||||
|
{ |
||||
|
var dto = new RuleDto(); |
||||
|
|
||||
|
SimpleMapper.Map(entity, dto); |
||||
|
SimpleMapper.Map(entity.Rule, dto); |
||||
|
|
||||
|
if (entity.Rule.Trigger != null) |
||||
|
{ |
||||
|
dto.Trigger = RuleTriggerDtoFactory.Create(entity.Rule.Trigger); |
||||
|
} |
||||
|
|
||||
|
if (entity.Rule.Action != null) |
||||
|
{ |
||||
|
dto.Action = RuleActionDtoFactory.Create(entity.Rule.Action); |
||||
|
} |
||||
|
|
||||
|
return dto; |
||||
|
} |
||||
|
|
||||
|
public static UpdateRule ToCommand(this UpdateRuleDto dto) |
||||
|
{ |
||||
|
var command = new UpdateRule |
||||
|
{ |
||||
|
Trigger = dto.Trigger?.ToTrigger(), Action = dto.Action?.ToAction() |
||||
|
}; |
||||
|
|
||||
|
return command; |
||||
|
} |
||||
|
|
||||
|
public static CreateRule ToCommand(this CreateRuleDto dto) |
||||
|
{ |
||||
|
var command = new CreateRule |
||||
|
{ |
||||
|
Trigger = dto.Trigger.ToTrigger(), Action = dto.Action.ToAction() |
||||
|
}; |
||||
|
|
||||
|
return command; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTriggerDtoFactory.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using Squidex.Controllers.Api.Rules.Models.Triggers; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models.Converters |
||||
|
{ |
||||
|
public sealed class RuleTriggerDtoFactory : IRuleTriggerVisitor<RuleTriggerDto> |
||||
|
{ |
||||
|
private static readonly RuleTriggerDtoFactory Instance = new RuleTriggerDtoFactory(); |
||||
|
|
||||
|
private RuleTriggerDtoFactory() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static RuleTriggerDto Create(RuleTrigger properties) |
||||
|
{ |
||||
|
return properties.Accept(Instance); |
||||
|
} |
||||
|
|
||||
|
public RuleTriggerDto Visit(ContentChangedTrigger trigger) |
||||
|
{ |
||||
|
return new ContentChangedTriggerDto |
||||
|
{ |
||||
|
Schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToList() |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,29 +1,27 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// UpdateWebhookDto.cs
|
// CreateRuleDto.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.ComponentModel.DataAnnotations; |
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
namespace Squidex.Controllers.Api.Webhooks.Models |
namespace Squidex.Controllers.Api.Rules.Models |
||||
{ |
{ |
||||
public sealed class UpdateWebhookDto |
public sealed class CreateRuleDto |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The url of the webhook.
|
/// The trigger properties.
|
||||
/// </summary>
|
/// </summary>
|
||||
[Required] |
[Required] |
||||
public Uri Url { get; set; } |
public RuleTriggerDto Trigger { get; set; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The schema settings.
|
/// The action properties.
|
||||
/// </summary>
|
/// </summary>
|
||||
[Required] |
[Required] |
||||
public List<WebhookSchemaDto> Schemas { get; set; } |
public RuleActionDto Action { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleActionDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Runtime.Serialization; |
||||
|
using Newtonsoft.Json; |
||||
|
using NJsonSchema.Converters; |
||||
|
using Squidex.Controllers.Api.Rules.Models.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models |
||||
|
{ |
||||
|
[JsonConverter(typeof(JsonInheritanceConverter), "actionType")] |
||||
|
[KnownType(typeof(WebhookActionDto))] |
||||
|
public abstract class RuleActionDto |
||||
|
{ |
||||
|
public abstract RuleAction ToAction(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using NodaTime; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models |
||||
|
{ |
||||
|
public sealed class RuleDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The id of the rule.
|
||||
|
/// </summary>
|
||||
|
public Guid Id { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The user that has created the rule.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public RefToken CreatedBy { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The user that has updated the rule.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public RefToken LastModifiedBy { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The date and time when the rule has been created.
|
||||
|
/// </summary>
|
||||
|
public Instant Created { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The date and time when the rule has been modified last.
|
||||
|
/// </summary>
|
||||
|
public Instant LastModified { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The version of the rule.
|
||||
|
/// </summary>
|
||||
|
public int Version { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The trigger properties.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public RuleTriggerDto Trigger { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The action properties.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public RuleActionDto Action { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines if the rule is enabled.
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -1,23 +1,23 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// WebhookEventsDto.cs
|
// RuleEventsDto.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Webhooks.Models |
namespace Squidex.Controllers.Api.Rules.Models |
||||
{ |
{ |
||||
public sealed class WebhookEventsDto |
public sealed class RuleEventsDto |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The total number of webhook events.
|
/// The total number of rule events.
|
||||
/// </summary>
|
/// </summary>
|
||||
public long Total { get; set; } |
public long Total { get; set; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The webhook events.
|
/// The rule events.
|
||||
/// </summary>
|
/// </summary>
|
||||
public WebhookEventDto[] Items { get; set; } |
public RuleEventDto[] Items { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleTriggerDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Runtime.Serialization; |
||||
|
using Newtonsoft.Json; |
||||
|
using NJsonSchema.Converters; |
||||
|
using Squidex.Controllers.Api.Rules.Models.Triggers; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models |
||||
|
{ |
||||
|
[JsonConverter(typeof(JsonInheritanceConverter), "triggerType")] |
||||
|
[KnownType(typeof(ContentChangedTriggerDto))] |
||||
|
public abstract class RuleTriggerDto |
||||
|
{ |
||||
|
public abstract RuleTrigger ToTrigger(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ContentChangedTriggerDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Linq; |
||||
|
using NJsonSchema.Annotations; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules.Models.Triggers |
||||
|
{ |
||||
|
[JsonSchema("ContentChanged")] |
||||
|
public sealed class ContentChangedTriggerDto : RuleTriggerDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The schema settings.
|
||||
|
/// </summary>
|
||||
|
[Required] |
||||
|
public List<ContentChangedTriggerSchemaDto> Schemas { get; set; } |
||||
|
|
||||
|
public override RuleTrigger ToTrigger() |
||||
|
{ |
||||
|
return new ContentChangedTrigger |
||||
|
{ |
||||
|
Schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToList() |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,29 +1,23 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// CreateWebhookDto.cs
|
// UpdateRuleDto.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
using System; |
namespace Squidex.Controllers.Api.Rules.Models |
||||
using System.Collections.Generic; |
|
||||
using System.ComponentModel.DataAnnotations; |
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Webhooks.Models |
|
||||
{ |
{ |
||||
public sealed class CreateWebhookDto |
public sealed class UpdateRuleDto |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// The url of the webhook.
|
/// The trigger properties.
|
||||
/// </summary>
|
/// </summary>
|
||||
[Required] |
public RuleTriggerDto Trigger { get; set; } |
||||
public Uri Url { get; set; } |
|
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The schema settings.
|
/// The action properties.
|
||||
/// </summary>
|
/// </summary>
|
||||
[Required] |
public RuleActionDto Action { get; set; } |
||||
public List<WebhookSchemaDto> Schemas { get; set; } |
|
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,245 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RulesController.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using NodaTime; |
||||
|
using NSwag.Annotations; |
||||
|
using Squidex.Controllers.Api.Rules.Models; |
||||
|
using Squidex.Controllers.Api.Rules.Models.Converters; |
||||
|
using Squidex.Domain.Apps.Read.Rules.Repositories; |
||||
|
using Squidex.Domain.Apps.Write.Rules.Commands; |
||||
|
using Squidex.Infrastructure.CQRS.Commands; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Pipeline; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Rules |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Manages and retrieves information about schemas.
|
||||
|
/// </summary>
|
||||
|
[ApiAuthorize] |
||||
|
[ApiExceptionFilter] |
||||
|
[AppApi] |
||||
|
[SwaggerTag(nameof(Rules))] |
||||
|
[MustBeAppDeveloper] |
||||
|
public sealed class RulesController : ControllerBase |
||||
|
{ |
||||
|
private readonly IRuleRepository rulesRepository; |
||||
|
private readonly IRuleEventRepository ruleEventsRepository; |
||||
|
|
||||
|
public RulesController(ICommandBus commandBus, |
||||
|
IRuleRepository rulesRepository, |
||||
|
IRuleEventRepository ruleEventsRepository) |
||||
|
: base(commandBus) |
||||
|
{ |
||||
|
this.rulesRepository = rulesRepository; |
||||
|
this.ruleEventsRepository = ruleEventsRepository; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get rules.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 200 => Rules returned.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
[HttpGet] |
||||
|
[Route("apps/{app}/rules/")] |
||||
|
[ProducesResponseType(typeof(RuleDto[]), 200)] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> GetRules(string app) |
||||
|
{ |
||||
|
var rules = await rulesRepository.QueryByAppAsync(App.Id); |
||||
|
|
||||
|
var response = rules.Select(r => r.ToModel()); |
||||
|
|
||||
|
return Ok(response); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create a new rule.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="request">The rule object that needs to be added to the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 201 => Rule created.
|
||||
|
/// 400 => Rule is not valid.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPost] |
||||
|
[Route("apps/{app}/rules/")] |
||||
|
[ProducesResponseType(typeof(EntityCreatedDto), 201)] |
||||
|
[ProducesResponseType(typeof(ErrorDto), 400)] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> PostRule(string app, [FromBody] CreateRuleDto request) |
||||
|
{ |
||||
|
var command = request.ToCommand(); |
||||
|
|
||||
|
var context = await CommandBus.PublishAsync(command); |
||||
|
|
||||
|
var result = context.Result<EntityCreatedResult<Guid>>(); |
||||
|
var response = new EntityCreatedDto { Id = result.IdOrValue.ToString(), Version = result.Version }; |
||||
|
|
||||
|
return CreatedAtAction(nameof(GetRules), new { app }, response); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Update a rule.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the rule to update.</param>
|
||||
|
/// <param name="request">The rule object that needs to be added to the app.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Rule updated.
|
||||
|
/// 400 => Rule is not valid.
|
||||
|
/// 404 => Rule or app not found.
|
||||
|
/// </returns>
|
||||
|
/// <remarks>
|
||||
|
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
|
||||
|
/// </remarks>
|
||||
|
[HttpPut] |
||||
|
[Route("apps/{app}/rules/{id}/")] |
||||
|
[ProducesResponseType(typeof(ErrorDto), 400)] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> PutRule(string app, Guid id, [FromBody] UpdateRuleDto request) |
||||
|
{ |
||||
|
var command = request.ToCommand(); |
||||
|
|
||||
|
await CommandBus.PublishAsync(command); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enable a rule.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the rule to enable.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Rule enabled.
|
||||
|
/// 400 => Rule already enabled.
|
||||
|
/// 404 => Rule or app not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPut] |
||||
|
[Route("apps/{app}/rules/{id}/enable/")] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> EnableRule(string app, Guid id) |
||||
|
{ |
||||
|
await CommandBus.PublishAsync(new EnableRule { RuleId = id }); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disable a rule.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the rule to disable.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Rule disabled.
|
||||
|
/// 400 => Rule already disabled.
|
||||
|
/// 404 => Rule or app not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPut] |
||||
|
[Route("apps/{app}/rules/{id}/disable/")] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> DisableRule(string app, Guid id) |
||||
|
{ |
||||
|
await CommandBus.PublishAsync(new DisableRule { RuleId = id }); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Delete a rule.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The id of the rule to delete.</param>
|
||||
|
/// <returns>
|
||||
|
/// 204 => Rule has been deleted.
|
||||
|
/// 404 => Rule or app not found.
|
||||
|
/// </returns>
|
||||
|
[HttpDelete] |
||||
|
[Route("apps/{app}/rules/{id}/")] |
||||
|
[ApiCosts(1)] |
||||
|
public async Task<IActionResult> DeleteRule(string app, Guid id) |
||||
|
{ |
||||
|
await CommandBus.PublishAsync(new DeleteRule { RuleId = id }); |
||||
|
|
||||
|
return NoContent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get rule events.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="skip">The number of events to skip.</param>
|
||||
|
/// <param name="take">The number of events to take.</param>
|
||||
|
/// <returns>
|
||||
|
/// 200 => Rule events returned.
|
||||
|
/// 404 => App not found.
|
||||
|
/// </returns>
|
||||
|
[HttpGet] |
||||
|
[Route("apps/{app}/rules/events/")] |
||||
|
[ProducesResponseType(typeof(RuleEventsDto), 200)] |
||||
|
[ApiCosts(0)] |
||||
|
public async Task<IActionResult> GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20) |
||||
|
{ |
||||
|
var taskForItems = ruleEventsRepository.QueryByAppAsync(App.Id, skip, take); |
||||
|
var taskForCount = ruleEventsRepository.CountByAppAsync(App.Id); |
||||
|
|
||||
|
await Task.WhenAll(taskForItems, taskForCount); |
||||
|
|
||||
|
var response = new RuleEventsDto |
||||
|
{ |
||||
|
Total = taskForCount.Result, |
||||
|
Items = taskForItems.Result.Select(x => |
||||
|
{ |
||||
|
var itemModel = new RuleEventDto(); |
||||
|
|
||||
|
SimpleMapper.Map(x, itemModel); |
||||
|
SimpleMapper.Map(x.Job, itemModel); |
||||
|
|
||||
|
return itemModel; |
||||
|
}).ToArray() |
||||
|
}; |
||||
|
|
||||
|
return Ok(response); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enqueue the event to be send.
|
||||
|
/// </summary>
|
||||
|
/// <param name="app">The name of the app.</param>
|
||||
|
/// <param name="id">The event to enqueue.</param>
|
||||
|
/// <returns>
|
||||
|
/// 200 => Rule enqueued.
|
||||
|
/// 404 => App or rule event not found.
|
||||
|
/// </returns>
|
||||
|
[HttpPut] |
||||
|
[Route("apps/{app}/rules/events/{id}/")] |
||||
|
[ApiCosts(0)] |
||||
|
public async Task<IActionResult> PutEvent(string app, Guid id) |
||||
|
{ |
||||
|
var entity = await ruleEventsRepository.FindAsync(id); |
||||
|
|
||||
|
if (entity == null) |
||||
|
{ |
||||
|
return NotFound(); |
||||
|
} |
||||
|
|
||||
|
await ruleEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant()); |
||||
|
|
||||
|
return Ok(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,89 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhookDto.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.ComponentModel.DataAnnotations; |
|
||||
using NodaTime; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Webhooks.Models |
|
||||
{ |
|
||||
public sealed class WebhookDto |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The id of the webhook.
|
|
||||
/// </summary>
|
|
||||
public Guid Id { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The user that has created the webhook.
|
|
||||
/// </summary>
|
|
||||
[Required] |
|
||||
public RefToken CreatedBy { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The user that has updated the webhook.
|
|
||||
/// </summary>
|
|
||||
[Required] |
|
||||
public RefToken LastModifiedBy { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The date and time when the webhook has been created.
|
|
||||
/// </summary>
|
|
||||
public Instant Created { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The date and time when the webhook has been modified last.
|
|
||||
/// </summary>
|
|
||||
public Instant LastModified { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The version of the webhook.
|
|
||||
/// </summary>
|
|
||||
public int Version { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of succceeded calls.
|
|
||||
/// </summary>
|
|
||||
public long TotalSucceeded { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of failed calls.
|
|
||||
/// </summary>
|
|
||||
public long TotalFailed { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of timedout calls.
|
|
||||
/// </summary>
|
|
||||
public long TotalTimedout { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The average response time in milliseconds.
|
|
||||
/// </summary>
|
|
||||
public long AverageRequestTimeMs { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The url of the webhook.
|
|
||||
/// </summary>
|
|
||||
[Required] |
|
||||
public Uri Url { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The shared secret that is used to calculate the signature.
|
|
||||
/// </summary>
|
|
||||
[Required] |
|
||||
public string SharedSecret { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The schema settings.
|
|
||||
/// </summary>
|
|
||||
[Required] |
|
||||
public List<WebhookSchemaDto> Schemas { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,220 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// WebhooksController.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using NodaTime; |
|
||||
using NSwag.Annotations; |
|
||||
using Squidex.Controllers.Api.Webhooks.Models; |
|
||||
using Squidex.Domain.Apps.Core.Webhooks; |
|
||||
using Squidex.Domain.Apps.Read.Webhooks.Repositories; |
|
||||
using Squidex.Domain.Apps.Write.Webhooks.Commands; |
|
||||
using Squidex.Infrastructure.CQRS.Commands; |
|
||||
using Squidex.Infrastructure.Reflection; |
|
||||
using Squidex.Pipeline; |
|
||||
|
|
||||
namespace Squidex.Controllers.Api.Webhooks |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Manages and retrieves information about schemas.
|
|
||||
/// </summary>
|
|
||||
[ApiAuthorize] |
|
||||
[ApiExceptionFilter] |
|
||||
[AppApi] |
|
||||
[SwaggerTag(nameof(Webhooks))] |
|
||||
[MustBeAppDeveloper] |
|
||||
public sealed class WebhooksController : ControllerBase |
|
||||
{ |
|
||||
private readonly IWebhookRepository webhooksRepository; |
|
||||
private readonly IWebhookEventRepository webhookEventsRepository; |
|
||||
|
|
||||
public WebhooksController(ICommandBus commandBus, |
|
||||
IWebhookRepository webhooksRepository, |
|
||||
IWebhookEventRepository webhookEventsRepository) |
|
||||
: base(commandBus) |
|
||||
{ |
|
||||
this.webhooksRepository = webhooksRepository; |
|
||||
this.webhookEventsRepository = webhookEventsRepository; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get webhooks.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <returns>
|
|
||||
/// 200 => Webhooks returned.
|
|
||||
/// 404 => App not found.
|
|
||||
/// </returns>
|
|
||||
[HttpGet] |
|
||||
[Route("apps/{app}/webhooks/")] |
|
||||
[ProducesResponseType(typeof(WebhookDto[]), 200)] |
|
||||
[ApiCosts(1)] |
|
||||
public async Task<IActionResult> GetWebhooks(string app) |
|
||||
{ |
|
||||
var webhooks = await webhooksRepository.QueryByAppAsync(App.Id); |
|
||||
|
|
||||
var response = webhooks.Select(w => |
|
||||
{ |
|
||||
var totalCount = w.TotalTimedout + w.TotalSucceeded + w.TotalFailed; |
|
||||
var totalAverage = totalCount == 0 ? 0 : w.TotalRequestTime / totalCount; |
|
||||
|
|
||||
var schemas = w.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchemaDto())).ToList(); |
|
||||
|
|
||||
return SimpleMapper.Map(w, new WebhookDto { AverageRequestTimeMs = totalAverage, Schemas = schemas }); |
|
||||
}); |
|
||||
|
|
||||
return Ok(response); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Create a new webhook.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <param name="request">The webhook object that needs to be added to the app.</param>
|
|
||||
/// <returns>
|
|
||||
/// 201 => Webhook created.
|
|
||||
/// 400 => Webhook is not valid.
|
|
||||
/// 404 => App not found.
|
|
||||
/// </returns>
|
|
||||
/// <remarks>
|
|
||||
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
|
|
||||
/// </remarks>
|
|
||||
[HttpPost] |
|
||||
[Route("apps/{app}/webhooks/")] |
|
||||
[ProducesResponseType(typeof(EntityCreatedDto), 201)] |
|
||||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|
||||
[ApiCosts(1)] |
|
||||
public async Task<IActionResult> PostWebhook(string app, [FromBody] CreateWebhookDto request) |
|
||||
{ |
|
||||
var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList(); |
|
||||
|
|
||||
var command = new CreateWebhook { Url = request.Url, Schemas = schemas }; |
|
||||
|
|
||||
var context = await CommandBus.PublishAsync(command); |
|
||||
|
|
||||
var result = context.Result<EntityCreatedResult<Guid>>(); |
|
||||
var response = new WebhookCreatedDto { Id = result.IdOrValue, SharedSecret = command.SharedSecret, Version = result.Version }; |
|
||||
|
|
||||
return CreatedAtAction(nameof(GetWebhooks), new { app }, response); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Update a webhook.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <param name="id">The id of the webhook to update.</param>
|
|
||||
/// <param name="request">The webhook object that needs to be added to the app.</param>
|
|
||||
/// <returns>
|
|
||||
/// 203 => Webhook updated.
|
|
||||
/// 400 => Webhook is not valid.
|
|
||||
/// 404 => Webhook or app not found.
|
|
||||
/// </returns>
|
|
||||
/// <remarks>
|
|
||||
/// All events for the specified schemas will be sent to the url. The timeout is 2 seconds.
|
|
||||
/// </remarks>
|
|
||||
[HttpPut] |
|
||||
[Route("apps/{app}/webhooks/{id}/")] |
|
||||
[ProducesResponseType(typeof(ErrorDto), 400)] |
|
||||
[ApiCosts(1)] |
|
||||
public async Task<IActionResult> PutWebhook(string app, Guid id, [FromBody] CreateWebhookDto request) |
|
||||
{ |
|
||||
var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList(); |
|
||||
|
|
||||
var command = new UpdateWebhook { WebhookId = id, Url = request.Url, Schemas = schemas }; |
|
||||
|
|
||||
await CommandBus.PublishAsync(command); |
|
||||
|
|
||||
return NoContent(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Delete a webhook.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <param name="id">The id of the webhook to delete.</param>
|
|
||||
/// <returns>
|
|
||||
/// 204 => Webhook has been deleted.
|
|
||||
/// 404 => Webhook or app not found.
|
|
||||
/// </returns>
|
|
||||
[HttpDelete] |
|
||||
[Route("apps/{app}/webhooks/{id}/")] |
|
||||
[ApiCosts(1)] |
|
||||
public async Task<IActionResult> DeleteWebhook(string app, Guid id) |
|
||||
{ |
|
||||
await CommandBus.PublishAsync(new DeleteWebhook { WebhookId = id }); |
|
||||
|
|
||||
return NoContent(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Get webhook events.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <param name="skip">The number of events to skip.</param>
|
|
||||
/// <param name="take">The number of events to take.</param>
|
|
||||
/// <returns>
|
|
||||
/// 200 => Webhook events returned.
|
|
||||
/// 404 => App not found.
|
|
||||
/// </returns>
|
|
||||
[HttpGet] |
|
||||
[Route("apps/{app}/webhooks/events/")] |
|
||||
[ProducesResponseType(typeof(WebhookEventsDto), 200)] |
|
||||
[ApiCosts(0)] |
|
||||
public async Task<IActionResult> GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20) |
|
||||
{ |
|
||||
var taskForItems = webhookEventsRepository.QueryByAppAsync(App.Id, skip, take); |
|
||||
var taskForCount = webhookEventsRepository.CountByAppAsync(App.Id); |
|
||||
|
|
||||
await Task.WhenAll(taskForItems, taskForCount); |
|
||||
|
|
||||
var response = new WebhookEventsDto |
|
||||
{ |
|
||||
Total = taskForCount.Result, |
|
||||
Items = taskForItems.Result.Select(x => |
|
||||
{ |
|
||||
var itemModel = new WebhookEventDto(); |
|
||||
|
|
||||
SimpleMapper.Map(x, itemModel); |
|
||||
SimpleMapper.Map(x.Job, itemModel); |
|
||||
|
|
||||
return itemModel; |
|
||||
}).ToArray() |
|
||||
}; |
|
||||
|
|
||||
return Ok(response); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Enqueue the event to be send.
|
|
||||
/// </summary>
|
|
||||
/// <param name="app">The name of the app.</param>
|
|
||||
/// <param name="id">The event to enqueue.</param>
|
|
||||
/// <returns>
|
|
||||
/// 200 => Webhook enqueued.
|
|
||||
/// 404 => App or webhook event not found.
|
|
||||
/// </returns>
|
|
||||
[HttpPut] |
|
||||
[Route("apps/{app}/webhooks/events/{id}/")] |
|
||||
[ApiCosts(0)] |
|
||||
public async Task<IActionResult> PutEvent(string app, Guid id) |
|
||||
{ |
|
||||
var entity = await webhookEventsRepository.FindAsync(id); |
|
||||
|
|
||||
if (entity == null) |
|
||||
{ |
|
||||
return NotFound(); |
|
||||
} |
|
||||
|
|
||||
await webhookEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant()); |
|
||||
|
|
||||
return Ok(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,102 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RuleEnqueuerTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FakeItEasy; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Core.Rules; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Actions; |
||||
|
using Squidex.Domain.Apps.Core.Rules.Triggers; |
||||
|
using Squidex.Domain.Apps.Events.Contents; |
||||
|
using Squidex.Domain.Apps.Read.Rules.Repositories; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Read.Rules |
||||
|
{ |
||||
|
public class RuleEnqueuerTests |
||||
|
{ |
||||
|
private readonly IRuleRepository ruleRepository = A.Fake<IRuleRepository>(); |
||||
|
private readonly IRuleEventRepository ruleEventRepository = A.Fake<IRuleEventRepository>(); |
||||
|
private readonly RuleService ruleService = A.Fake<RuleService>(); |
||||
|
private readonly Instant now = SystemClock.Instance.GetCurrentInstant(); |
||||
|
private readonly NamedId<Guid> appId = new NamedId<Guid>(Guid.NewGuid(), "my-app"); |
||||
|
private readonly RuleEnqueuer sut; |
||||
|
|
||||
|
public RuleEnqueuerTests() |
||||
|
{ |
||||
|
sut = new RuleEnqueuer( |
||||
|
ruleEventRepository, |
||||
|
ruleRepository, |
||||
|
ruleService); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_contents_filter_for_events_filter() |
||||
|
{ |
||||
|
Assert.Equal(".*", sut.EventsFilter); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_return_type_name_for_name() |
||||
|
{ |
||||
|
Assert.Equal(typeof(RuleEnqueuer).Name, sut.Name); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public Task Should_do_nothing_on_clear() |
||||
|
{ |
||||
|
return sut.ClearAsync(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_update_repositories_on_with_jobs_from_sender() |
||||
|
{ |
||||
|
var @event = Envelope.Create(new ContentCreated { AppId = appId }); |
||||
|
|
||||
|
var rule1 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); |
||||
|
var rule2 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); |
||||
|
var rule3 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); |
||||
|
|
||||
|
var job1 = new RuleJob { Created = now }; |
||||
|
var job2 = new RuleJob { Created = now }; |
||||
|
|
||||
|
var ruleEntity1 = A.Fake<IRuleEntity>(); |
||||
|
var ruleEntity2 = A.Fake<IRuleEntity>(); |
||||
|
var ruleEntity3 = A.Fake<IRuleEntity>(); |
||||
|
|
||||
|
A.CallTo(() => ruleEntity1.Rule).Returns(rule1); |
||||
|
A.CallTo(() => ruleEntity2.Rule).Returns(rule2); |
||||
|
A.CallTo(() => ruleEntity3.Rule).Returns(rule3); |
||||
|
|
||||
|
A.CallTo(() => ruleRepository.QueryCachedByAppAsync(appId.Id)) |
||||
|
.Returns(new List<IRuleEntity> { ruleEntity1, ruleEntity2, ruleEntity3 }); |
||||
|
|
||||
|
A.CallTo(() => ruleService.CreateJob(rule1, @event)) |
||||
|
.Returns(job1); |
||||
|
|
||||
|
A.CallTo(() => ruleService.CreateJob(rule2, @event)) |
||||
|
.Returns(job2); |
||||
|
|
||||
|
A.CallTo(() => ruleService.CreateJob(rule3, @event)) |
||||
|
.Returns(null); |
||||
|
|
||||
|
await sut.On(@event); |
||||
|
|
||||
|
A.CallTo(() => ruleEventRepository.EnqueueAsync(job1, now)) |
||||
|
.MustHaveHappened(); |
||||
|
|
||||
|
A.CallTo(() => ruleEventRepository.EnqueueAsync(job2, now)) |
||||
|
.MustHaveHappened(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue