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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
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>
|
|||
/// The url of the webhook.
|
|||
/// The trigger properties.
|
|||
/// </summary>
|
|||
[Required] |
|||
public Uri Url { get; set; } |
|||
public RuleTriggerDto Trigger { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The schema settings.
|
|||
/// The action properties.
|
|||
/// </summary>
|
|||
[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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Controllers.Api.Webhooks.Models |
|||
namespace Squidex.Controllers.Api.Rules.Models |
|||
{ |
|||
public sealed class WebhookEventsDto |
|||
public sealed class RuleEventsDto |
|||
{ |
|||
/// <summary>
|
|||
/// The total number of webhook events.
|
|||
/// The total number of rule events.
|
|||
/// </summary>
|
|||
public long Total { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The webhook events.
|
|||
/// The rule events.
|
|||
/// </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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace Squidex.Controllers.Api.Webhooks.Models |
|||
namespace Squidex.Controllers.Api.Rules.Models |
|||
{ |
|||
public sealed class CreateWebhookDto |
|||
public sealed class UpdateRuleDto |
|||
{ |
|||
/// <summary>
|
|||
/// The url of the webhook.
|
|||
/// The trigger properties.
|
|||
/// </summary>
|
|||
[Required] |
|||
public Uri Url { get; set; } |
|||
public RuleTriggerDto Trigger { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The schema settings.
|
|||
/// The action properties.
|
|||
/// </summary>
|
|||
[Required] |
|||
public List<WebhookSchemaDto> Schemas { get; set; } |
|||
public RuleActionDto Action { 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