From 9e1540e909c67331a9ecf47bcdb8e71ce127b2b7 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 29 Oct 2017 21:08:03 +0100 Subject: [PATCH] Write logic updated and tested. --- .../Rules/Json/RuleConverter.cs | 1 - .../Webhooks/WebhookSchema.cs | 25 -- .../Rules/Utils/RuleEventDispatcher.cs | 37 --- .../Webhooks/WebhookCreated.cs | 18 -- .../Webhooks/WebhookDeleted.cs | 17 -- .../Webhooks/WebhookEditEvent.cs | 21 -- .../Webhooks/WebhookUpdated.cs | 17 -- .../Apps/MongoAppRepository_EventHandling.cs | 1 - .../MongoAssetRepository_EventHandling.cs | 1 - .../MongoContentRepository_EventHandling.cs | 1 - .../{Utils => }/EntityMapper.cs | 2 +- .../History/MongoHistoryEventRepository.cs | 1 - .../{Utils => }/MongoCollectionExtensions.cs | 2 +- .../Rules/MongoRuleEntity.cs | 41 +++ .../MongoRuleEventEntity.cs} | 45 +--- .../MongoRuleEventRepository.cs} | 50 ++-- .../Rules/MongoRuleRepository.cs | 90 +++++++ .../MongoRuleRepository_EventHandling.cs | 97 +++++++ .../MongoSchemaRepository_EventHandling.cs | 1 - .../Webhooks/MongoWebhookEntity.cs | 74 ------ .../Webhooks/MongoWebhookRepository.cs | 119 --------- .../MongoWebhookRepository_EventHandling.cs | 99 -------- .../Implementations/CachingAppProvider.cs | 1 - .../{Utils => }/CachingProviderBase.cs | 2 +- .../Contents/Edm/EdmModelBuilder.cs | 1 - .../Contents/GraphQL/CachingGraphQLService.cs | 1 - .../Rules/IRuleEventEntity.cs | 1 + .../Repositories/IRuleEventRepository.cs | 2 +- .../Rules/RuleDequeuer.cs | 4 +- .../Implementations/CachingSchemaProvider.cs | 1 - .../Webhooks/IWebhookEntity.cs | 31 --- .../Webhooks/IWebhookEventEntity.cs | 27 -- .../Repositories/IWebhookEventRepository.cs | 35 --- .../Repositories/IWebhookRepository.cs | 23 -- .../Webhooks/WebhookDequeuer.cs | 160 ------------ .../Webhooks/WebhookEnqueuer.cs | 140 ---------- .../Webhooks/WebhookJob.cs | 32 --- .../Webhooks/WebhookSender.cs | 98 ------- .../Commands/CreateRule.cs} | 6 +- .../Commands/DeleteRule.cs} | 6 +- .../Rules/Commands/DisableRule.cs} | 10 +- .../Rules/Commands/EnableRule.cs} | 10 +- .../Commands/RuleAggregateCommand.cs} | 10 +- .../Rules/Commands/RuleEditCommand.cs} | 12 +- .../Rules/Commands/UpdateRule.cs | 14 + .../Rules/Guards/GuardRule.cs | 107 ++++++++ .../Rules/Guards/RuleActionValidator.cs | 40 +++ .../Rules/Guards/RuleTriggerValidator.cs | 54 ++++ .../Rules/RuleCommandMiddleware.cs | 92 +++++++ .../Rules/RuleDomainObject.cs | 118 +++++++++ .../Webhooks/Commands/CreateWebhook.cs | 23 -- .../Webhooks/Commands/WebhookEditCommand.cs | 21 -- .../Webhooks/Guards/GuardWebhook.cs | 61 ----- .../Webhooks/WebhookCommandMiddleware.cs | 72 ------ .../Webhooks/WebhookDomainObject.cs | 82 ------ src/Squidex/Squidex.csproj | 4 +- .../Guards/Actions/WebhookActionTests.cs | 48 ++++ .../Rules/Guards/GuardRuleTests.cs | 158 ++++++++++++ .../Triggers/ContentChangedTriggerTests.cs | 85 +++++++ .../Rules/RuleCommandMiddlewareTests.cs | 117 +++++++++ .../Rules/RuleDomainObjectTests.cs | 240 ++++++++++++++++++ .../Webhooks/Guards/GuardWebhookTests.cs | 138 ---------- .../Webhooks/WebhookCommandMiddlewareTests.cs | 115 --------- .../Webhooks/WebhookDomainObjectTests.cs | 159 ------------ 64 files changed, 1374 insertions(+), 1747 deletions(-) delete mode 100644 src/Squidex.Domain.Apps.Core.Model/Webhooks/WebhookSchema.cs delete mode 100644 src/Squidex.Domain.Apps.Events/Webhooks/WebhookCreated.cs delete mode 100644 src/Squidex.Domain.Apps.Events/Webhooks/WebhookDeleted.cs delete mode 100644 src/Squidex.Domain.Apps.Events/Webhooks/WebhookEditEvent.cs delete mode 100644 src/Squidex.Domain.Apps.Events/Webhooks/WebhookUpdated.cs rename src/Squidex.Domain.Apps.Read.MongoDb/{Utils => }/EntityMapper.cs (98%) rename src/Squidex.Domain.Apps.Read.MongoDb/{Utils => }/MongoCollectionExtensions.cs (98%) create mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEntity.cs rename src/Squidex.Domain.Apps.Read.MongoDb/{Webhooks/MongoWebhookEventEntity.cs => Rules/MongoRuleEventEntity.cs} (59%) rename src/Squidex.Domain.Apps.Read.MongoDb/{Webhooks/MongoWebhookEventRepository.cs => Rules/MongoRuleEventRepository.cs} (66%) create mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs create mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEntity.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository_EventHandling.cs rename src/Squidex.Domain.Apps.Read/{Utils => }/CachingProviderBase.cs (95%) delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEntity.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEventEntity.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookEventRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookRepository.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/WebhookDequeuer.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/WebhookJob.cs delete mode 100644 src/Squidex.Domain.Apps.Read/Webhooks/WebhookSender.cs rename src/Squidex.Domain.Apps.Write/{Webhooks/Commands/UpdateWebhook.cs => Rules/Commands/CreateRule.cs} (71%) rename src/Squidex.Domain.Apps.Write/{Webhooks/Commands/DeleteWebhook.cs => Rules/Commands/DeleteRule.cs} (70%) rename src/{Squidex.Domain.Apps.Read/Webhooks/WebhookResult.cs => Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs} (67%) rename src/{Squidex.Domain.Apps.Read/Webhooks/WebhookJobResult.cs => Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs} (67%) rename src/Squidex.Domain.Apps.Write/{Webhooks/Commands/WebhookAggregateCommand.cs => Rules/Commands/RuleAggregateCommand.cs} (64%) rename src/{Squidex.Domain.Apps.Events/Webhooks/WebhookEvent.cs => Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs} (55%) create mode 100644 src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs create mode 100644 src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs create mode 100644 src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs create mode 100644 src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs create mode 100644 src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs create mode 100644 src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs delete mode 100644 src/Squidex.Domain.Apps.Write/Webhooks/Commands/CreateWebhook.cs delete mode 100644 src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookEditCommand.cs delete mode 100644 src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs delete mode 100644 src/Squidex.Domain.Apps.Write/Webhooks/WebhookCommandMiddleware.cs delete mode 100644 src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs create mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs delete mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs delete mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs delete mode 100644 tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs index 27f7f10c5..cd3e9707f 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs @@ -6,7 +6,6 @@ // All rights reserved. // ========================================================================== -using System; using Newtonsoft.Json; using Squidex.Infrastructure.Json; diff --git a/src/Squidex.Domain.Apps.Core.Model/Webhooks/WebhookSchema.cs b/src/Squidex.Domain.Apps.Core.Model/Webhooks/WebhookSchema.cs deleted file mode 100644 index 169e85b7d..000000000 --- a/src/Squidex.Domain.Apps.Core.Model/Webhooks/WebhookSchema.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// WebhookSchema.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; - -namespace Squidex.Domain.Apps.Core.Webhooks -{ - public sealed class WebhookSchema - { - public Guid SchemaId { get; set; } - - public bool SendCreate { get; set; } - - public bool SendUpdate { get; set; } - - public bool SendDelete { get; set; } - - public bool SendPublish { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs index 45b03161d..8de79c181 100644 --- a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs +++ b/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs @@ -6,12 +6,7 @@ // All rights reserved. // ========================================================================== -using System.Linq; 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.Webhooks; -using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Events.Rules.Utils { @@ -22,21 +17,6 @@ namespace Squidex.Domain.Apps.Events.Rules.Utils return new Rule(@event.Trigger, @event.Action); } - public static Rule Create(WebhookCreated @event) - { - return new Rule(CreateTrigger(@event), CreateAction(@event)); - } - - public static void Apply(this Rule rule, WebhookUpdated @event) - { - rule.Update(CreateTrigger(@event)); - - if (rule.Action is WebhookAction webhookAction) - { - webhookAction.Url = @event.Url; - } - } - public static void Apply(this Rule rule, RuleUpdated @event) { if (@event.Trigger != null) @@ -59,22 +39,5 @@ namespace Squidex.Domain.Apps.Events.Rules.Utils { rule.Disable(); } - - private static WebhookAction CreateAction(WebhookCreated @event) - { - var action = new WebhookAction { Url = @event.Url, SharedSecret = @event.SharedSecret }; - - return action; - } - - private static ContentChangedTrigger CreateTrigger(WebhookEditEvent @event) - { - var trigger = new ContentChangedTrigger - { - Schemas = @event.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToList() - }; - - return trigger; - } } } diff --git a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookCreated.cs b/src/Squidex.Domain.Apps.Events/Webhooks/WebhookCreated.cs deleted file mode 100644 index 1cab7fdce..000000000 --- a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookCreated.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// WebhookCreated.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Infrastructure.CQRS.Events; - -namespace Squidex.Domain.Apps.Events.Webhooks -{ - [EventType(nameof(WebhookCreated))] - public sealed class WebhookCreated : WebhookEditEvent - { - public string SharedSecret { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookDeleted.cs b/src/Squidex.Domain.Apps.Events/Webhooks/WebhookDeleted.cs deleted file mode 100644 index a04e86ab6..000000000 --- a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookDeleted.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// WebhookDeleted.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Infrastructure.CQRS.Events; - -namespace Squidex.Domain.Apps.Events.Webhooks -{ - [EventType(nameof(WebhookDeleted), 2)] - public sealed class WebhookDeleted : WebhookEvent - { - } -} diff --git a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookEditEvent.cs b/src/Squidex.Domain.Apps.Events/Webhooks/WebhookEditEvent.cs deleted file mode 100644 index 02dde09b3..000000000 --- a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookEditEvent.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// WebhookEditEvent.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Webhooks; - -namespace Squidex.Domain.Apps.Events.Webhooks -{ - public abstract class WebhookEditEvent : WebhookEvent - { - public Uri Url { get; set; } - - public List Schemas { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookUpdated.cs b/src/Squidex.Domain.Apps.Events/Webhooks/WebhookUpdated.cs deleted file mode 100644 index c40793935..000000000 --- a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookUpdated.cs +++ /dev/null @@ -1,17 +0,0 @@ -// ========================================================================== -// WebhookUpdated.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Infrastructure.CQRS.Events; - -namespace Squidex.Domain.Apps.Events.Webhooks -{ - [EventType(nameof(WebhookUpdated))] - public sealed class WebhookUpdated : WebhookEditEvent - { - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs index 0fdab29f8..51c627fec 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs @@ -13,7 +13,6 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps.Utils; -using Squidex.Domain.Apps.Read.MongoDb.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs index 1915a40bd..2685c24fc 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using MongoDB.Driver; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Domain.Apps.Read.MongoDb.Utils; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs index b76240365..077fa6cd1 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -13,7 +13,6 @@ using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Read.MongoDb.Utils; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Utils/EntityMapper.cs b/src/Squidex.Domain.Apps.Read.MongoDb/EntityMapper.cs similarity index 98% rename from src/Squidex.Domain.Apps.Read.MongoDb/Utils/EntityMapper.cs rename to src/Squidex.Domain.Apps.Read.MongoDb/EntityMapper.cs index d4b999cf3..0557c28f4 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Utils/EntityMapper.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/EntityMapper.cs @@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.Utils +namespace Squidex.Domain.Apps.Read.MongoDb { public static class EntityMapper { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs index 15610b5c8..53368fd51 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs @@ -14,7 +14,6 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Read.History; using Squidex.Domain.Apps.Read.History.Repositories; -using Squidex.Domain.Apps.Read.MongoDb.Utils; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Utils/MongoCollectionExtensions.cs b/src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs similarity index 98% rename from src/Squidex.Domain.Apps.Read.MongoDb/Utils/MongoCollectionExtensions.cs rename to src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs index 78cd17175..c9cc5cd8c 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Utils/MongoCollectionExtensions.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs @@ -14,7 +14,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.Utils +namespace Squidex.Domain.Apps.Read.MongoDb { public static class MongoCollectionExtensions { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEntity.cs new file mode 100644 index 000000000..be9369804 --- /dev/null +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEntity.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// MongoRuleEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Infrastructure; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Apps.Read.MongoDb.Rules +{ + public class MongoRuleEntity : MongoEntity, IRuleEntity + { + [BsonRequired] + [BsonElement] + public Guid AppId { get; set; } + + [BsonRequired] + [BsonElement] + public RefToken CreatedBy { get; set; } + + [BsonRequired] + [BsonElement] + public RefToken LastModifiedBy { get; set; } + + [BsonRequired] + [BsonElement] + public long Version { get; set; } + + [BsonRequired] + [BsonElement] + [BsonJson] + public Rule Rule { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs similarity index 59% rename from src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventEntity.cs rename to src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs index 18f212967..0b31e6a12 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventEntity.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs @@ -1,5 +1,5 @@ // ========================================================================== -// MongoWebhookEventEntity.cs +// MongoRuleEventEntity.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,43 +9,30 @@ using System; using MongoDB.Bson.Serialization.Attributes; using NodaTime; -using Squidex.Domain.Apps.Read.Webhooks; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Read.Rules; using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks +namespace Squidex.Domain.Apps.Read.MongoDb.Rules { - public sealed class MongoWebhookEventEntity : MongoEntity, IWebhookEventEntity + public sealed class MongoRuleEventEntity : MongoEntity, IRuleEventEntity { - private WebhookJob job; - [BsonRequired] [BsonElement] public Guid AppId { get; set; } [BsonRequired] [BsonElement] - public long Version { get; set; } - - [BsonRequired] - [BsonElement] - public Uri RequestUrl { get; set; } - - [BsonRequired] - [BsonElement] - public string RequestBody { get; set; } - - [BsonRequired] - [BsonElement] - public string RequestSignature { get; set; } + public string EventName { get; set; } [BsonRequired] [BsonElement] - public string EventName { get; set; } + public string LastDump { get; set; } [BsonRequired] [BsonElement] - public string LastDump { get; set; } + public int NumCalls { get; set; } [BsonRequired] [BsonElement] @@ -57,23 +44,19 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks [BsonRequired] [BsonElement] - public int NumCalls { get; set; } + public bool IsSending { get; set; } [BsonRequired] [BsonElement] - public bool IsSending { get; set; } + public RuleResult Result { get; set; } [BsonRequired] [BsonElement] - public WebhookResult Result { get; set; } + public RuleJobResult JobResult { get; set; } [BsonRequired] [BsonElement] - public WebhookJobResult JobResult { get; set; } - - public WebhookJob Job - { - get { return job ?? (job = SimpleMapper.Map(this, new WebhookJob())); } - } + [BsonJson] + public RuleJob Job { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs similarity index 66% rename from src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventRepository.cs rename to src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs index d6a4325fc..0bdaa4198 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEventRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs @@ -1,5 +1,5 @@ // ========================================================================== -// MongoWebhookEventRepository.cs +// MongoRuleEventRepository.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -12,19 +12,21 @@ using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using NodaTime; -using Squidex.Domain.Apps.Read.Webhooks; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Read.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks +namespace Squidex.Domain.Apps.Read.MongoDb.Rules { - public sealed class MongoWebhookEventRepository : MongoRepositoryBase, IWebhookEventRepository + public sealed class MongoRuleEventRepository : MongoRepositoryBase, IRuleEventRepository { private readonly IClock clock; - public MongoWebhookEventRepository(IMongoDatabase database, IClock clock) + public MongoRuleEventRepository(IMongoDatabase database, IClock clock) : base(database) { Guard.NotNull(clock, nameof(clock)); @@ -34,10 +36,10 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks protected override string CollectionName() { - return "WebhookEvents"; + return "RuleEvents"; } - protected override Task SetupCollectionAsync(IMongoCollection collection) + protected override Task SetupCollectionAsync(IMongoCollection collection) { return Task.WhenAll( collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt).Descending(x => x.IsSending)), @@ -45,14 +47,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero })); } - public Task QueryPendingAsync(Func callback, CancellationToken cancellationToken = default(CancellationToken)) + public Task QueryPendingAsync(Func callback, CancellationToken cancellationToken = default(CancellationToken)) { var now = clock.GetCurrentInstant(); return Collection.Find(x => x.NextAttempt < now && !x.IsSending).ForEachAsync(callback, cancellationToken); } - public async Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20) + public async Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20) { var webhookEventEntities = await Collection.Find(x => x.AppId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created) @@ -61,7 +63,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks return webhookEventEntities; } - public async Task FindAsync(Guid id) + public async Task FindAsync(Guid id) { var webhookEventEntity = await Collection.Find(x => x.Id == id) @@ -80,33 +82,33 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks return Collection.UpdateOneAsync(x => x.Id == id, Update.Set(x => x.NextAttempt, nextAttempt)); } - public Task TraceSendingAsync(Guid jobId) + public Task EnqueueAsync(RuleJob job, Instant nextAttempt) { - return Collection.UpdateOneAsync(x => x.Id == jobId, Update.Set(x => x.IsSending, true)); + var entity = SimpleMapper.Map(job, new MongoRuleEventEntity { Created = clock.GetCurrentInstant(), NextAttempt = nextAttempt }); + + return Collection.InsertOneIfNotExistsAsync(entity); } - public Task EnqueueAsync(WebhookJob job, Instant nextAttempt) + public Task MarkSendingAsync(Guid jobId) { - var entity = SimpleMapper.Map(job, new MongoWebhookEventEntity { Created = clock.GetCurrentInstant(), NextAttempt = nextAttempt }); - - return Collection.InsertOneIfNotExistsAsync(entity); + return Collection.UpdateOneAsync(x => x.Id == jobId, Update.Set(x => x.IsSending, true)); } - public Task TraceSentAsync(Guid jobId, string dump, WebhookResult result, TimeSpan elapsed, Instant? nextAttempt) + public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, TimeSpan elapsed, Instant? nextAttempt) { - WebhookJobResult jobResult; + RuleJobResult jobResult; - if (result != WebhookResult.Success && nextAttempt == null) + if (result != RuleResult.Success && nextAttempt == null) { - jobResult = WebhookJobResult.Failed; + jobResult = RuleJobResult.Failed; } - else if (result != WebhookResult.Success && nextAttempt.HasValue) + else if (result != RuleResult.Success && nextAttempt.HasValue) { - jobResult = WebhookJobResult.Retry; + jobResult = RuleJobResult.Retry; } else { - jobResult = WebhookJobResult.Success; + jobResult = RuleJobResult.Success; } return Collection.UpdateOneAsync(x => x.Id == jobId, diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs new file mode 100644 index 000000000..c4dc37beb --- /dev/null +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs @@ -0,0 +1,90 @@ +// ========================================================================== +// MongoRuleRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Apps.Read.MongoDb.Rules +{ + public partial class MongoRuleRepository : MongoRepositoryBase, IRuleRepository, IEventConsumer + { + private static readonly List EmptyRules = new List(); + private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); + private Dictionary> inMemoryWebhooks; + + public MongoRuleRepository(IMongoDatabase database) + : base(database) + { + } + + protected override string CollectionName() + { + return "Projections_Rules"; + } + + protected override Task SetupCollectionAsync(IMongoCollection collection) + { + return Task.WhenAll(collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId))); + } + + public async Task> QueryByAppAsync(Guid appId) + { + var entities = + await Collection.Find(x => x.AppId == appId) + .ToListAsync(); + + return entities.OfType().ToList(); + } + + public async Task> QueryCachedByAppAsync(Guid appId) + { + await EnsureRulesLoadedAsync(); + + return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyRules; + } + + private async Task EnsureRulesLoadedAsync() + { + if (inMemoryWebhooks == null) + { + try + { + await lockObject.WaitAsync(); + + if (inMemoryWebhooks == null) + { + inMemoryWebhooks = new Dictionary>(); + + var webhooks = + await Collection.Find(new BsonDocument()) + .ToListAsync(); + + foreach (var webhook in webhooks) + { + inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); + } + } + } + finally + { + lockObject.Release(); + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs new file mode 100644 index 000000000..4de792863 --- /dev/null +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs @@ -0,0 +1,97 @@ +// ========================================================================== +// MongoRuleRepository_EventHandling.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using MongoDB.Driver; +using Squidex.Domain.Apps.Events.Rules; +using Squidex.Domain.Apps.Events.Rules.Utils; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Dispatching; + +namespace Squidex.Domain.Apps.Read.MongoDb.Rules +{ + public partial class MongoRuleRepository + { + public string Name + { + get { return GetType().Name; } + } + + public string EventsFilter + { + get { return "^rules-"; } + } + + public Task On(Envelope @event) + { + return this.DispatchActionAsync(@event.Payload, @event.Headers); + } + + protected async Task On(RuleCreated @event, EnvelopeHeaders headers) + { + await EnsureRulesLoadedAsync(); + + await Collection.CreateAsync(@event, headers, w => + { + w.Rule = RuleEventDispatcher.Create(@event); + + inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + }); + } + + protected async Task On(RuleUpdated @event, EnvelopeHeaders headers) + { + await EnsureRulesLoadedAsync(); + + await Collection.UpdateAsync(@event, headers, w => + { + w.Rule.Apply(@event); + + inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + }); + } + + protected async Task On(RuleEnabled @event, EnvelopeHeaders headers) + { + await EnsureRulesLoadedAsync(); + + await Collection.UpdateAsync(@event, headers, w => + { + w.Rule.Apply(@event); + + inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + }); + } + + protected async Task On(RuleDisabled @event, EnvelopeHeaders headers) + { + await EnsureRulesLoadedAsync(); + + await Collection.UpdateAsync(@event, headers, w => + { + w.Rule.Apply(@event); + + inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + }); + } + + protected async Task On(RuleDeleted @event, EnvelopeHeaders headers) + { + await EnsureRulesLoadedAsync(); + + inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId); + + await Collection.DeleteManyAsync(x => x.Id == @event.RuleId); + } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs index b4a662186..772620b38 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs @@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Domain.Apps.Events.Schemas.Old; using Squidex.Domain.Apps.Events.Schemas.Utils; -using Squidex.Domain.Apps.Read.MongoDb.Utils; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEntity.cs deleted file mode 100644 index 239ea346a..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookEntity.cs +++ /dev/null @@ -1,74 +0,0 @@ -// ========================================================================== -// MongoWebhookEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Core.Webhooks; -using Squidex.Domain.Apps.Read.Webhooks; -using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks -{ - public class MongoWebhookEntity : MongoEntity, IWebhookEntity - { - [BsonRequired] - [BsonElement] - public Uri Url { get; set; } - - [BsonRequired] - [BsonElement] - public Guid AppId { get; set; } - - [BsonRequired] - [BsonElement] - public long Version { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken CreatedBy { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken LastModifiedBy { get; set; } - - [BsonRequired] - [BsonElement] - public string SharedSecret { get; set; } - - [BsonRequired] - [BsonElement] - public long TotalSucceeded { get; set; } - - [BsonRequired] - [BsonElement] - public long TotalFailed { get; set; } - - [BsonRequired] - [BsonElement] - public long TotalTimedout { get; set; } - - [BsonRequired] - [BsonElement] - public long TotalRequestTime { get; set; } - - [BsonRequired] - [BsonElement] - public List Schemas { get; set; } - - [BsonRequired] - [BsonElement] - public List SchemaIds { get; set; } - - IEnumerable IWebhookEntity.Schemas - { - get { return Schemas; } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository.cs deleted file mode 100644 index d4dc24ac9..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository.cs +++ /dev/null @@ -1,119 +0,0 @@ -// ========================================================================== -// MongoWebhookRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MongoDB.Bson; -using MongoDB.Driver; -using Squidex.Domain.Apps.Read.Webhooks; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks -{ - public partial class MongoWebhookRepository : MongoRepositoryBase, IWebhookRepository, IEventConsumer - { - private static readonly List EmptyWebhooks = new List(); - private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); - private Dictionary> inMemoryWebhooks; - - public MongoWebhookRepository(IMongoDatabase database) - : base(database) - { - } - - protected override string CollectionName() - { - return "Projections_SchemaWebhooks"; - } - - protected override Task SetupCollectionAsync(IMongoCollection collection) - { - return Task.WhenAll( - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaIds))); - } - - public async Task> QueryByAppAsync(Guid appId) - { - var entities = - await Collection.Find(x => x.AppId == appId) - .ToListAsync(); - - return entities.OfType().ToList(); - } - - public async Task> QueryCachedByAppAsync(Guid appId) - { - await EnsureWebooksLoadedAsync(); - - return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyWebhooks; - } - - public async Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed) - { - var webhookEntity = - await Collection.Find(x => x.Id == webhookId) - .FirstOrDefaultAsync(); - - if (webhookEntity != null) - { - switch (result) - { - case WebhookResult.Success: - webhookEntity.TotalSucceeded++; - break; - case WebhookResult.Failed: - webhookEntity.TotalFailed++; - break; - case WebhookResult.Timeout: - webhookEntity.TotalTimedout++; - break; - } - - webhookEntity.TotalRequestTime += (long)elapsed.TotalMilliseconds; - - await Collection.ReplaceOneAsync(x => x.Id == webhookId, webhookEntity); - } - } - - private async Task EnsureWebooksLoadedAsync() - { - if (inMemoryWebhooks == null) - { - try - { - await lockObject.WaitAsync(); - - if (inMemoryWebhooks == null) - { - inMemoryWebhooks = new Dictionary>(); - - var webhooks = - await Collection.Find(new BsonDocument()) - .ToListAsync(); - - foreach (var webhook in webhooks) - { - inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); - } - } - } - finally - { - lockObject.Release(); - } - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository_EventHandling.cs deleted file mode 100644 index 9646e8d62..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Webhooks/MongoWebhookRepository_EventHandling.cs +++ /dev/null @@ -1,99 +0,0 @@ -// ========================================================================== -// MongoSchemaWebhookRepository_EventHandling.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Linq; -using System.Threading.Tasks; -using MongoDB.Driver; -using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Events.Webhooks; -using Squidex.Domain.Apps.Read.MongoDb.Utils; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Read.MongoDb.Webhooks -{ - public partial class MongoWebhookRepository - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "(^webhook-)|(^schema-)"; } - } - - public Task On(Envelope @event) - { - return this.DispatchActionAsync(@event.Payload, @event.Headers); - } - - protected async Task On(WebhookCreated @event, EnvelopeHeaders headers) - { - await EnsureWebooksLoadedAsync(); - - await Collection.CreateAsync(@event, headers, w => - { - SimpleMapper.Map(@event, w); - - w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); - - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); - }); - } - - protected async Task On(WebhookUpdated @event, EnvelopeHeaders headers) - { - await EnsureWebooksLoadedAsync(); - - await Collection.UpdateAsync(@event, headers, w => - { - SimpleMapper.Map(@event, w); - - w.SchemaIds = w.Schemas.Select(x => x.SchemaId).ToList(); - - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); - }); - } - - protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers) - { - await EnsureWebooksLoadedAsync(); - - var webhooks = - await Collection.Find(t => t.SchemaIds.Contains(@event.SchemaId.Id)) - .ToListAsync(); - - foreach (var webhook in webhooks) - { - webhook.Schemas.RemoveAll(s => s.SchemaId == @event.SchemaId.Id); - - webhook.SchemaIds = webhook.Schemas.Select(x => x.SchemaId).ToList(); - - inMemoryWebhooks.GetOrAddNew(webhook.AppId).RemoveAll(x => x.Id == webhook.Id); - inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); - - await Collection.ReplaceOneAsync(x => x.Id == webhook.Id, webhook); - } - } - - protected async Task On(WebhookDeleted @event, EnvelopeHeaders headers) - { - await EnsureWebooksLoadedAsync(); - - inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.WebhookId); - - await Collection.DeleteManyAsync(x => x.Id == @event.WebhookId); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs index 8e82a4a31..becf14af4 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs +++ b/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Read.Apps.Repositories; -using Squidex.Domain.Apps.Read.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.CQRS.Events; diff --git a/src/Squidex.Domain.Apps.Read/Utils/CachingProviderBase.cs b/src/Squidex.Domain.Apps.Read/CachingProviderBase.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read/Utils/CachingProviderBase.cs rename to src/Squidex.Domain.Apps.Read/CachingProviderBase.cs index 6e5330c04..5883b29ab 100644 --- a/src/Squidex.Domain.Apps.Read/Utils/CachingProviderBase.cs +++ b/src/Squidex.Domain.Apps.Read/CachingProviderBase.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Caching.Memory; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Utils +namespace Squidex.Domain.Apps.Read { public abstract class CachingProviderBase { diff --git a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs b/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs index 14fa93c78..7f3fd63ce 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs @@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Utils; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Read.Contents.Edm diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs index cfabcb279..f20d7cf4c 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs @@ -15,7 +15,6 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Schemas.Repositories; -using Squidex.Domain.Apps.Read.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.Tasks; diff --git a/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs b/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs index 43dea2b55..631ad2322 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs @@ -8,6 +8,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Domain.Apps.Read.Rules { diff --git a/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs b/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs index ded48c233..f67c074e8 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Read.Rules.Repositories Task MarkSendingAsync(Guid jobId); - Task TraceSentAsync(Guid jobId, string dump, RuleResult result, TimeSpan elapsed, Instant? nextCall); + Task MarkSentAsync(Guid jobId, string dump, RuleResult result, TimeSpan elapsed, Instant? nextCall); Task QueryPendingAsync(Func callback, CancellationToken cancellationToken = default(CancellationToken)); diff --git a/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs b/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs index bcaa3d718..3edb332c4 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Read.Rules { var job = @event.Job; - var response = await ruleService.InvokeAsync(job.ActionName, job.Details); + var response = await ruleService.InvokeAsync(job.ActionName, job.ActionData); Instant? nextCall = null; @@ -133,7 +133,7 @@ namespace Squidex.Domain.Apps.Read.Rules } } - await ruleEventRepository.TraceSentAsync(@event.Id, response.Dump, response.Result, response.Elapsed, nextCall); + await ruleEventRepository.MarkSentAsync(@event.Id, response.Dump, response.Result, response.Elapsed, nextCall); } catch (Exception ex) { diff --git a/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs b/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs index e7d0ef9e1..ab0a609bd 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs +++ b/src/Squidex.Domain.Apps.Read/Schemas/Services/Implementations/CachingSchemaProvider.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Read.Schemas.Repositories; -using Squidex.Domain.Apps.Read.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.CQRS.Events; diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEntity.cs b/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEntity.cs deleted file mode 100644 index 62ba20035..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEntity.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ========================================================================== -// IWebhookEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Webhooks; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public interface IWebhookEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion - { - Uri Url { get; } - - long TotalSucceeded { get; } - - long TotalFailed { get; } - - long TotalTimedout { get; } - - long TotalRequestTime { get; } - - string SharedSecret { get; } - - IEnumerable Schemas { get; } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEventEntity.cs b/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEventEntity.cs deleted file mode 100644 index e35b23747..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/IWebhookEventEntity.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// IWebhookEventEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using NodaTime; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public interface IWebhookEventEntity : IEntity - { - WebhookJob Job { get; } - - Instant? NextAttempt { get; } - - WebhookResult Result { get; } - - WebhookJobResult JobResult { get; } - - int NumCalls { get; } - - string LastDump { get; } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookEventRepository.cs b/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookEventRepository.cs deleted file mode 100644 index 26d6d0ffa..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookEventRepository.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ========================================================================== -// IWebhookEventRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using NodaTime; - -namespace Squidex.Domain.Apps.Read.Webhooks.Repositories -{ - public interface IWebhookEventRepository - { - Task EnqueueAsync(WebhookJob job, Instant nextAttempt); - - Task EnqueueAsync(Guid id, Instant nextAttempt); - - Task TraceSendingAsync(Guid jobId); - - Task TraceSentAsync(Guid jobId, string dump, WebhookResult result, TimeSpan elapsed, Instant? nextCall); - - Task QueryPendingAsync(Func callback, CancellationToken cancellationToken = default(CancellationToken)); - - Task CountByAppAsync(Guid appId); - - Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); - - Task FindAsync(Guid id); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookRepository.cs b/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookRepository.cs deleted file mode 100644 index b0a4dde1f..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/Repositories/IWebhookRepository.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// ISchemaWebhookRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Squidex.Domain.Apps.Read.Webhooks.Repositories -{ - public interface IWebhookRepository - { - Task TraceSentAsync(Guid webhookId, WebhookResult result, TimeSpan elapsed); - - Task> QueryByAppAsync(Guid appId); - - Task> QueryCachedByAppAsync(Guid appId); - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookDequeuer.cs b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookDequeuer.cs deleted file mode 100644 index 1d585018d..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookDequeuer.cs +++ /dev/null @@ -1,160 +0,0 @@ -// ========================================================================== -// WebhookDequeuer.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using NodaTime; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.Timers; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public sealed class WebhookDequeuer : DisposableObjectBase, IExternalSystem - { - private readonly ActionBlock requestBlock; - private readonly TransformBlock blockBlock; - private readonly IWebhookEventRepository webhookEventRepository; - private readonly IWebhookRepository webhookRepository; - private readonly WebhookSender webhookSender; - private readonly CompletionTimer timer; - private readonly ISemanticLog log; - private readonly IClock clock; - - public WebhookDequeuer(WebhookSender webhookSender, - IWebhookEventRepository webhookEventRepository, - IWebhookRepository webhookRepository, - IClock clock, - ISemanticLog log) - { - Guard.NotNull(webhookEventRepository, nameof(webhookEventRepository)); - Guard.NotNull(webhookRepository, nameof(webhookRepository)); - Guard.NotNull(webhookSender, nameof(webhookSender)); - Guard.NotNull(clock, nameof(clock)); - Guard.NotNull(log, nameof(log)); - - this.webhookEventRepository = webhookEventRepository; - this.webhookRepository = webhookRepository; - this.webhookSender = webhookSender; - - this.clock = clock; - - this.log = log; - - requestBlock = - new ActionBlock(MakeRequestAsync, - new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); - - blockBlock = - new TransformBlock(x => BlockAsync(x), - new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, BoundedCapacity = 1 }); - - blockBlock.LinkTo(requestBlock, new DataflowLinkOptions { PropagateCompletion = true }); - - timer = new CompletionTimer(5000, QueryAsync); - } - - protected override void DisposeObject(bool disposing) - { - if (disposing) - { - timer.StopAsync().Wait(); - - blockBlock.Complete(); - requestBlock.Completion.Wait(); - } - } - - public void Connect() - { - } - - public void Next() - { - timer.SkipCurrentDelay(); - } - - private async Task QueryAsync(CancellationToken cancellationToken) - { - try - { - await webhookEventRepository.QueryPendingAsync(blockBlock.SendAsync, cancellationToken); - } - catch (Exception ex) - { - log.LogError(ex, w => w - .WriteProperty("action", "QueueWebhookEvents") - .WriteProperty("status", "Failed")); - } - } - - private async Task BlockAsync(IWebhookEventEntity @event) - { - try - { - await webhookEventRepository.TraceSendingAsync(@event.Id); - - return @event; - } - catch (Exception ex) - { - log.LogError(ex, w => w - .WriteProperty("action", "BlockWebhookEvent") - .WriteProperty("status", "Failed")); - - throw; - } - } - - private async Task MakeRequestAsync(IWebhookEventEntity @event) - { - try - { - var response = await webhookSender.SendAsync(@event.Job); - - Instant? nextCall = null; - - if (response.Result != WebhookResult.Success) - { - var now = clock.GetCurrentInstant(); - - switch (@event.NumCalls) - { - case 0: - nextCall = now.Plus(Duration.FromMinutes(5)); - break; - case 1: - nextCall = now.Plus(Duration.FromHours(1)); - break; - case 2: - nextCall = now.Plus(Duration.FromHours(5)); - break; - case 3: - nextCall = now.Plus(Duration.FromHours(6)); - break; - } - } - - await Task.WhenAll( - webhookRepository.TraceSentAsync(@event.Job.WebhookId, response.Result, response.Elapsed), - webhookEventRepository.TraceSentAsync(@event.Id, response.Dump, response.Result, response.Elapsed, nextCall)); - } - catch (Exception ex) - { - log.LogError(ex, w => w - .WriteProperty("action", "SendWebhookEvent") - .WriteProperty("status", "Failed")); - - throw; - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs deleted file mode 100644 index 0e3be5546..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookEnqueuer.cs +++ /dev/null @@ -1,140 +0,0 @@ -// ========================================================================== -// WebhookEnqueuer.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using NodaTime; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Webhooks; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public sealed class WebhookEnqueuer : IEventConsumer - { - private const string ContentPrefix = "Content"; - private static readonly Duration ExpirationTime = Duration.FromDays(2); - private readonly IWebhookEventRepository webhookEventRepository; - private readonly IWebhookRepository webhookRepository; - private readonly IClock clock; - private readonly TypeNameRegistry typeNameRegistry; - private readonly JsonSerializer webhookSerializer; - - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^content-"; } - } - - public WebhookEnqueuer(TypeNameRegistry typeNameRegistry, - IWebhookEventRepository webhookEventRepository, - IWebhookRepository webhookRepository, - IClock clock, - JsonSerializer webhookSerializer) - { - Guard.NotNull(webhookEventRepository, nameof(webhookEventRepository)); - Guard.NotNull(webhookSerializer, nameof(webhookSerializer)); - Guard.NotNull(webhookRepository, nameof(webhookRepository)); - Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); - Guard.NotNull(clock, nameof(clock)); - - this.webhookEventRepository = webhookEventRepository; - this.webhookSerializer = webhookSerializer; - this.webhookRepository = webhookRepository; - - this.clock = clock; - - this.typeNameRegistry = typeNameRegistry; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - - public async Task On(Envelope @event) - { - if (@event.Payload is ContentEvent contentEvent) - { - var eventType = typeNameRegistry.GetName(@event.Payload.GetType()); - - var webhooks = await webhookRepository.QueryCachedByAppAsync(contentEvent.AppId.Id); - - var matchingWebhooks = webhooks.Where(w => w.Schemas.Any(s => Matchs(s, contentEvent))).ToList(); - - if (matchingWebhooks.Count > 0) - { - var now = clock.GetCurrentInstant(); - - var eventPayload = CreatePayload(@event, eventType); - var eventName = $"{contentEvent.SchemaId.Name.ToPascalCase()}{CreateContentEventName(eventType)}"; - - foreach (var webhook in matchingWebhooks) - { - await EnqueueJobAsync(eventPayload, webhook, contentEvent, eventName, now); - } - } - } - } - - private async Task EnqueueJobAsync(string payload, IWebhookEntity webhook, AppEvent contentEvent, string eventName, Instant now) - { - var signature = $"{payload}{webhook.SharedSecret}".Sha256Base64(); - - var job = new WebhookJob - { - Id = Guid.NewGuid(), - AppId = contentEvent.AppId.Id, - RequestUrl = webhook.Url, - RequestBody = payload, - RequestSignature = signature, - EventName = eventName, - Expires = now.Plus(ExpirationTime), - WebhookId = webhook.Id - }; - - await webhookEventRepository.EnqueueAsync(job, now); - } - - private static bool Matchs(WebhookSchema webhookSchema, SchemaEvent @event) - { - return - (@event.SchemaId.Id == webhookSchema.SchemaId) && - (webhookSchema.SendCreate && @event is ContentCreated || - webhookSchema.SendUpdate && @event is ContentUpdated || - webhookSchema.SendDelete && @event is ContentDeleted || - webhookSchema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); - } - - private string CreatePayload(Envelope @event, string eventType) - { - return new JObject( - new JProperty("type", eventType), - new JProperty("payload", JObject.FromObject(@event.Payload, webhookSerializer)), - new JProperty("timestamp", @event.Headers.Timestamp().ToString())) - .ToString(Formatting.Indented); - } - - private static string CreateContentEventName(string eventType) - { - return eventType.StartsWith(ContentPrefix, StringComparison.Ordinal) ? eventType.Substring(ContentPrefix.Length) : eventType; - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookJob.cs b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookJob.cs deleted file mode 100644 index 8951c3da6..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookJob.cs +++ /dev/null @@ -1,32 +0,0 @@ -// ========================================================================== -// WebhookJob.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using NodaTime; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public sealed class WebhookJob - { - public Guid Id { get; set; } - - public Guid AppId { get; set; } - - public Guid WebhookId { get; set; } - - public Uri RequestUrl { get; set; } - - public string RequestBody { get; set; } - - public string RequestSignature { get; set; } - - public string EventName { get; set; } - - public Instant Expires { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookSender.cs b/src/Squidex.Domain.Apps.Read/Webhooks/WebhookSender.cs deleted file mode 100644 index 6a1b07993..000000000 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookSender.cs +++ /dev/null @@ -1,98 +0,0 @@ -// ========================================================================== -// WebhookSender.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Squidex.Infrastructure.Http; - -namespace Squidex.Domain.Apps.Read.Webhooks -{ - public class WebhookSender - { - private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); - - public virtual async Task<(string Dump, WebhookResult Result, TimeSpan Elapsed)> SendAsync(WebhookJob job) - { - try - { - var request = BuildRequest(job); - HttpResponseMessage response = null; - - var responseString = string.Empty; - - var isTimeout = false; - - var watch = Stopwatch.StartNew(); - - try - { - using (var client = new HttpClient { Timeout = Timeout }) - { - response = await client.SendAsync(request); - } - } - catch (TimeoutException) - { - isTimeout = true; - } - catch (OperationCanceledException) - { - isTimeout = true; - } - catch (Exception ex) - { - responseString = ex.Message; - } - finally - { - watch.Stop(); - } - - if (response != null) - { - responseString = await response.Content.ReadAsStringAsync(); - } - - var dump = DumpFormatter.BuildDump(request, response, job.RequestBody, responseString, watch.Elapsed, isTimeout); - - var result = WebhookResult.Failed; - - if (isTimeout) - { - result = WebhookResult.Timeout; - } - else if (response?.IsSuccessStatusCode == true) - { - result = WebhookResult.Success; - } - - return (dump, result, watch.Elapsed); - } - catch (Exception ex) - { - return (ex.Message, WebhookResult.Failed, TimeSpan.Zero); - } - } - - private static HttpRequestMessage BuildRequest(WebhookJob job) - { - var request = new HttpRequestMessage(HttpMethod.Post, job.RequestUrl) - { - Content = new StringContent(job.RequestBody, Encoding.UTF8, "application/json") - }; - - request.Headers.Add("X-Signature", job.RequestSignature); - request.Headers.Add("User-Agent", "Squidex Webhook"); - - return request; - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/UpdateWebhook.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs similarity index 71% rename from src/Squidex.Domain.Apps.Write/Webhooks/Commands/UpdateWebhook.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs index 22c33345f..34aaa4d21 100644 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/UpdateWebhook.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs @@ -1,14 +1,14 @@ // ========================================================================== -// UpdateWebhook.cs +// CreateRule.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Webhooks.Commands +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public sealed class UpdateWebhook : WebhookEditCommand + public sealed class CreateRule : RuleEditCommand { } } diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/DeleteWebhook.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs similarity index 70% rename from src/Squidex.Domain.Apps.Write/Webhooks/Commands/DeleteWebhook.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs index 462532dfe..c97f2c6b1 100644 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/DeleteWebhook.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs @@ -1,14 +1,14 @@ // ========================================================================== -// DeleteWebhook.cs +// DeleteRule.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Webhooks.Commands +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public sealed class DeleteWebhook : WebhookAggregateCommand + public sealed class DeleteRule : RuleAggregateCommand { } } diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookResult.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs similarity index 67% rename from src/Squidex.Domain.Apps.Read/Webhooks/WebhookResult.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs index bc8584b5b..ccfa2a9be 100644 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookResult.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs @@ -1,18 +1,14 @@ // ========================================================================== -// WebhookResult.cs +// DisableRule.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Webhooks +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public enum WebhookResult + public sealed class DisableRule : RuleAggregateCommand { - Pending, - Success, - Failed, - Timeout } } diff --git a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookJobResult.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs similarity index 67% rename from src/Squidex.Domain.Apps.Read/Webhooks/WebhookJobResult.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs index 57d68fc19..ac3bf7f4c 100644 --- a/src/Squidex.Domain.Apps.Read/Webhooks/WebhookJobResult.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs @@ -1,18 +1,14 @@ // ========================================================================== -// WebhookJobResult.cs +// EnableRule.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Webhooks +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public enum WebhookJobResult + public sealed class EnableRule : RuleAggregateCommand { - Pending, - Success, - Retry, - Failed } } diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookAggregateCommand.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs similarity index 64% rename from src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookAggregateCommand.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs index ec13cb8ef..4af936af5 100644 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs @@ -1,5 +1,5 @@ // ========================================================================== -// WebhookAggregateCommand.cs +// RuleAggregateCommand.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,15 +9,15 @@ using System; using Squidex.Infrastructure.CQRS.Commands; -namespace Squidex.Domain.Apps.Write.Webhooks.Commands +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public abstract class WebhookAggregateCommand : AppCommand, IAggregateCommand + public abstract class RuleAggregateCommand : AppCommand, IAggregateCommand { - public Guid WebhookId { get; set; } + public Guid RuleId { get; set; } Guid IAggregateCommand.AggregateId { - get { return WebhookId; } + get { return RuleId; } } } } diff --git a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookEvent.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs similarity index 55% rename from src/Squidex.Domain.Apps.Events/Webhooks/WebhookEvent.cs rename to src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs index 99fc08697..b63be728a 100644 --- a/src/Squidex.Domain.Apps.Events/Webhooks/WebhookEvent.cs +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs @@ -1,17 +1,19 @@ // ========================================================================== -// WebhookEvent.cs +// RuleEditCommand.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using System; +using Squidex.Domain.Apps.Core.Rules; -namespace Squidex.Domain.Apps.Events.Webhooks +namespace Squidex.Domain.Apps.Write.Rules.Commands { - public abstract class WebhookEvent : AppEvent + public abstract class RuleEditCommand : RuleAggregateCommand { - public Guid WebhookId { get; set; } + public RuleTrigger Trigger { get; set; } + + public RuleAction Action { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs b/src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs new file mode 100644 index 000000000..6fe43df4d --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// UpdateRule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Domain.Apps.Write.Rules.Commands +{ + public sealed class UpdateRule : RuleEditCommand + { + } +} diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs new file mode 100644 index 000000000..47a6cc129 --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs @@ -0,0 +1,107 @@ +// ========================================================================== +// GuardRule.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Write.Rules.Guards +{ + public static class GuardRule + { + public static Task CanCreate(CreateRule command, ISchemaProvider schemas) + { + Guard.NotNull(command, nameof(command)); + + return Validate.It(() => "Cannot create rule.", async error => + { + if (command.Trigger == null) + { + error(new ValidationError("Trigger must be defined.", nameof(command.Trigger))); + } + else + { + var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); + + errors.Foreach(error); + } + + if (command.Action == null) + { + error(new ValidationError("Trigger must be defined.", nameof(command.Action))); + } + else + { + var errors = await RuleActionValidator.ValidateAsync(command.Action); + + errors.Foreach(error); + } + }); + } + + public static Task CanUpdate(UpdateRule command, ISchemaProvider schemas) + { + Guard.NotNull(command, nameof(command)); + + return Validate.It(() => "Cannot update rule.", async error => + { + if (command.Trigger == null && command.Action == null) + { + error(new ValidationError("Either trigger or action must be defined.", nameof(command.Trigger), nameof(command.Action))); + } + + if (command.Trigger != null) + { + var errors = await RuleTriggerValidator.ValidateAsync(command.Trigger, schemas); + + errors.Foreach(error); + } + + if (command.Action != null) + { + var errors = await RuleActionValidator.ValidateAsync(command.Action); + + errors.Foreach(error); + } + }); + } + + public static void CanEnable(EnableRule command, Rule rule) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot enable rule.", error => + { + if (rule.IsEnabled) + { + error(new ValidationError("Rule is already enabled.")); + } + }); + } + + public static void CanDisable(DisableRule command, Rule rule) + { + Guard.NotNull(command, nameof(command)); + + Validate.It(() => "Cannot disable rule.", error => + { + if (!rule.IsEnabled) + { + error(new ValidationError("Rule is already disabled.")); + } + }); + } + + public static void CanDelete(DeleteRule command) + { + Guard.NotNull(command, nameof(command)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs new file mode 100644 index 000000000..4ead12956 --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// RuleActionValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Write.Rules.Guards +{ + public sealed class RuleActionValidator : IRuleActionVisitor>> + { + public static Task> ValidateAsync(RuleAction action) + { + Guard.NotNull(action, nameof(action)); + + var visitor = new RuleActionValidator(); + + return action.Accept(visitor); + } + + public Task> Visit(WebhookAction action) + { + var errors = new List(); + + if (action.Url == null || !action.Url.IsAbsoluteUri) + { + errors.Add(new ValidationError("Url must be specified and absolute.", nameof(action.Url))); + } + + return Task.FromResult>(errors); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs new file mode 100644 index 000000000..f9594c26f --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// RuleTriggerValidator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Write.Rules.Guards +{ + public sealed class RuleTriggerValidator : IRuleTriggerVisitor>> + { + public ISchemaProvider Schemas { get; } + + public RuleTriggerValidator(ISchemaProvider schemas) + { + Schemas = schemas; + } + + public static Task> ValidateAsync(RuleTrigger action, ISchemaProvider schemas) + { + Guard.NotNull(action, nameof(action)); + Guard.NotNull(schemas, nameof(schemas)); + + var visitor = new RuleTriggerValidator(schemas); + + return action.Accept(visitor); + } + + public async Task> Visit(ContentChangedTrigger trigger) + { + if (trigger.Schemas != null) + { + var schemaErrors = await Task.WhenAll( + trigger.Schemas.Select(async s => + await Schemas.FindSchemaByIdAsync(s.SchemaId) == null + ? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(trigger.Schemas)) + : null)); + + return schemaErrors.Where(x => x != null).ToList(); + } + + return new List(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs new file mode 100644 index 000000000..b0b2ed082 --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs @@ -0,0 +1,92 @@ +// ========================================================================== +// RuleCommandMiddleware.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Write.Rules.Guards; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Commands; +using Squidex.Infrastructure.Dispatching; + +namespace Squidex.Domain.Apps.Write.Rules +{ + public class RuleCommandMiddleware : ICommandMiddleware + { + private readonly IAggregateHandler handler; + private readonly ISchemaProvider schemas; + + public RuleCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) + { + Guard.NotNull(handler, nameof(handler)); + Guard.NotNull(schemas, nameof(schemas)); + + this.handler = handler; + this.schemas = schemas; + } + + protected Task On(CreateRule command, CommandContext context) + { + return handler.CreateAsync(context, async w => + { + await GuardRule.CanCreate(command, schemas); + + w.Create(command); + }); + } + + protected Task On(UpdateRule command, CommandContext context) + { + return handler.UpdateAsync(context, async c => + { + await GuardRule.CanUpdate(command, schemas); + + c.Update(command); + }); + } + + protected Task On(EnableRule command, CommandContext context) + { + return handler.UpdateAsync(context, r => + { + GuardRule.CanEnable(command, r.Rule); + + r.Enable(command); + }); + } + + protected Task On(DisableRule command, CommandContext context) + { + return handler.UpdateAsync(context, r => + { + GuardRule.CanDisable(command, r.Rule); + + r.Disable(command); + }); + } + + protected Task On(DeleteRule command, CommandContext context) + { + return handler.UpdateAsync(context, c => + { + GuardRule.CanDelete(command); + + c.Delete(command); + }); + } + + public async Task HandleAsync(CommandContext context, Func next) + { + if (!await this.DispatchActionAsync(context.Command, context)) + { + await next(); + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs b/src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs new file mode 100644 index 000000000..ccf2e4a57 --- /dev/null +++ b/src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs @@ -0,0 +1,118 @@ +// ========================================================================== +// RuleDomainObject.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Events.Rules; +using Squidex.Domain.Apps.Events.Rules.Utils; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS; +using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Write.Rules +{ + public class RuleDomainObject : DomainObjectBase + { + private Rule rule; + private bool isDeleted; + + public Rule Rule + { + get { return rule; } + } + + public RuleDomainObject(Guid id, int version) + : base(id, version) + { + } + + protected void On(RuleCreated @event) + { + rule = RuleEventDispatcher.Create(@event); + } + + protected void On(RuleUpdated @event) + { + rule.Apply(@event); + } + + protected void On(RuleEnabled @event) + { + rule.Apply(@event); + } + + protected void On(RuleDisabled @event) + { + rule.Apply(@event); + } + + protected void On(RuleDeleted @event) + { + isDeleted = true; + } + + public void Create(CreateRule command) + { + VerifyNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new RuleCreated())); + } + + public void Update(UpdateRule command) + { + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleUpdated())); + } + + public void Enable(EnableRule command) + { + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleEnabled())); + } + + public void Disable(DisableRule command) + { + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleDisabled())); + } + + public void Delete(DeleteRule command) + { + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new RuleDeleted())); + } + + private void VerifyNotCreated() + { + if (rule != null) + { + throw new DomainException("Webhook has already been created."); + } + } + + private void VerifyCreatedAndNotDeleted() + { + if (isDeleted || rule == null) + { + throw new DomainException("Webhook has already been deleted or not created yet."); + } + } + + protected override void DispatchEvent(Envelope @event) + { + this.DispatchAction(@event.Payload); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/CreateWebhook.cs b/src/Squidex.Domain.Apps.Write/Webhooks/Commands/CreateWebhook.cs deleted file mode 100644 index d6a76ea84..000000000 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/CreateWebhook.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// CreateWebhook.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Write.Webhooks.Commands -{ - public sealed class CreateWebhook : WebhookEditCommand - { - public string SharedSecret { get; } = RandomHash.New(); - - public CreateWebhook() - { - WebhookId = Guid.NewGuid(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookEditCommand.cs b/src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookEditCommand.cs deleted file mode 100644 index 2f8cc5d3f..000000000 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Commands/WebhookEditCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// WebhookEditCommand.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Webhooks; - -namespace Squidex.Domain.Apps.Write.Webhooks.Commands -{ - public abstract class WebhookEditCommand : WebhookAggregateCommand - { - public Uri Url { get; set; } - - public List Schemas { get; set; } = new List(); - } -} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs b/src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs deleted file mode 100644 index 13359a531..000000000 --- a/src/Squidex.Domain.Apps.Write/Webhooks/Guards/GuardWebhook.cs +++ /dev/null @@ -1,61 +0,0 @@ -// ========================================================================== -// GuardWebhook.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Schemas.Services; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Write.Webhooks.Guards -{ - public static class GuardWebhook - { - public static Task CanCreate(CreateWebhook command, ISchemaProvider schemas) - { - Guard.NotNull(command, nameof(command)); - - return Validate.It(() => "Cannot create webhook.", error => ValidateCommandAsync(command, error, schemas)); - } - - public static Task CanUpdate(UpdateWebhook command, ISchemaProvider schemas) - { - Guard.NotNull(command, nameof(command)); - - return Validate.It(() => "Cannot update webhook.", error => ValidateCommandAsync(command, error, schemas)); - } - - public static void CanDelete(DeleteWebhook command) - { - Guard.NotNull(command, nameof(command)); - } - - private static async Task ValidateCommandAsync(WebhookEditCommand command, Action error, ISchemaProvider schemas) - { - if (command.Url == null || !command.Url.IsAbsoluteUri) - { - error(new ValidationError("Url must be specified and absolute.", nameof(command.Url))); - } - - if (command.Schemas != null) - { - var schemaErrors = await Task.WhenAll( - command.Schemas.Select(async s => - await schemas.FindSchemaByIdAsync(s.SchemaId) == null - ? new ValidationError($"Schema {s.SchemaId} does not exist.", nameof(command.Schemas)) - : null)); - - foreach (var schemaError in schemaErrors.Where(x => x != null)) - { - error(schemaError); - } - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookCommandMiddleware.cs b/src/Squidex.Domain.Apps.Write/Webhooks/WebhookCommandMiddleware.cs deleted file mode 100644 index 13cee7474..000000000 --- a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookCommandMiddleware.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ========================================================================== -// WebhookCommandMiddleware.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Schemas.Services; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Domain.Apps.Write.Webhooks.Guards; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Dispatching; - -namespace Squidex.Domain.Apps.Write.Webhooks -{ - public class WebhookCommandMiddleware : ICommandMiddleware - { - private readonly IAggregateHandler handler; - private readonly ISchemaProvider schemas; - - public WebhookCommandMiddleware(IAggregateHandler handler, ISchemaProvider schemas) - { - Guard.NotNull(handler, nameof(handler)); - Guard.NotNull(schemas, nameof(schemas)); - - this.handler = handler; - this.schemas = schemas; - } - - protected async Task On(CreateWebhook command, CommandContext context) - { - await handler.CreateAsync(context, async w => - { - await GuardWebhook.CanCreate(command, schemas); - - w.Create(command); - }); - } - - protected async Task On(UpdateWebhook command, CommandContext context) - { - await handler.UpdateAsync(context, async c => - { - await GuardWebhook.CanUpdate(command, schemas); - - c.Update(command); - }); - } - - protected Task On(DeleteWebhook command, CommandContext context) - { - return handler.UpdateAsync(context, c => - { - GuardWebhook.CanDelete(command); - - c.Delete(command); - }); - } - - public async Task HandleAsync(CommandContext context, Func next) - { - if (!await this.DispatchActionAsync(context.Command, context)) - { - await next(); - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs b/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs deleted file mode 100644 index fee952cf7..000000000 --- a/src/Squidex.Domain.Apps.Write/Webhooks/WebhookDomainObject.cs +++ /dev/null @@ -1,82 +0,0 @@ -// ========================================================================== -// WebhookDomainObject.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Events.Webhooks; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS; -using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Write.Webhooks -{ - public class WebhookDomainObject : DomainObjectBase - { - private bool isDeleted; - private bool isCreated; - - public WebhookDomainObject(Guid id, int version) - : base(id, version) - { - } - - protected void On(WebhookCreated @event) - { - isCreated = true; - } - - protected void On(WebhookDeleted @event) - { - isDeleted = true; - } - - public void Create(CreateWebhook command) - { - VerifyNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new WebhookCreated())); - } - - public void Update(UpdateWebhook command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new WebhookUpdated())); - } - - public void Delete(DeleteWebhook command) - { - VerifyCreatedAndNotDeleted(); - - RaiseEvent(SimpleMapper.Map(command, new WebhookDeleted())); - } - - private void VerifyNotCreated() - { - if (isCreated) - { - throw new DomainException("Webhook has already been created."); - } - } - - private void VerifyCreatedAndNotDeleted() - { - if (isDeleted || !isCreated) - { - throw new DomainException("Webhook has already been deleted or not created yet."); - } - } - - protected override void DispatchEvent(Envelope @event) - { - this.DispatchAction(@event.Payload); - } - } -} diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index a7ffd5aa4..eff3313f2 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -62,11 +62,11 @@ - + - + diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs new file mode 100644 index 000000000..fe3487cce --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// WebhookActionTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Rules.Guards.Actions +{ + public sealed class WebhookActionTests + { + [Fact] + public async Task Should_add_error_if_url_is_null() + { + var action = new WebhookAction { Url = null }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_add_error_if_url_is_relative() + { + var action = new WebhookAction { Url = new Uri("/invalid", UriKind.Relative) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_url_is_absolute() + { + var action = new WebhookAction { Url = new Uri("https://squidex.io", UriKind.Absolute) }; + + var errors = await RuleActionValidator.ValidateAsync(action); + + Assert.Empty(errors); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs new file mode 100644 index 000000000..1699e64d8 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs @@ -0,0 +1,158 @@ +// ========================================================================== +// GuardRuleTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Infrastructure; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Rules.Guards +{ + public class GuardRuleTests + { + private readonly Uri validUrl = new Uri("https://squidex.io"); + private readonly Rule rule = new Rule(new ContentChangedTrigger(), new WebhookAction()); + private readonly ISchemaProvider schemas = A.Fake(); + + public GuardRuleTests() + { + A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) + .Returns(A.Fake()); + } + + [Fact] + public async Task CanCreate_should_throw_exception_if_trigger_null() + { + var command = new CreateRule + { + Trigger = null, + Action = new WebhookAction + { + Url = validUrl + } + }; + + await Assert.ThrowsAsync(() => GuardRule.CanCreate(command, schemas)); + } + + [Fact] + public async Task CanCreate_should_throw_exception_if_action_null() + { + var command = new CreateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = new List() + }, + Action = null + }; + + await Assert.ThrowsAsync(() => GuardRule.CanCreate(command, schemas)); + } + + [Fact] + public async Task CanCreate_should_not_throw_exception_if_trigger_and_action_valid() + { + var command = new CreateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = new List() + }, + Action = new WebhookAction + { + Url = validUrl + } + }; + + await GuardRule.CanCreate(command, schemas); + } + + [Fact] + public async Task CanUpdate_should_throw_exception_if_action_and_trigger_are_null() + { + var command = new UpdateRule(); + + await Assert.ThrowsAsync(() => GuardRule.CanUpdate(command, schemas)); + } + + [Fact] + public async Task CanUpdate_should_not_throw_exception_if_trigger_and_action_valid() + { + var command = new UpdateRule + { + Trigger = new ContentChangedTrigger + { + Schemas = new List() + }, + Action = new WebhookAction + { + Url = validUrl + } + }; + + await GuardRule.CanUpdate(command, schemas); + } + + [Fact] + public void CanEnable_should_throw_exception_if_rule_enabled() + { + var command = new EnableRule(); + + rule.Enable(); + + Assert.Throws(() => GuardRule.CanEnable(command, rule)); + } + + [Fact] + public void CanEnable_should_not_throw_exception_if_rule_disabled() + { + var command = new EnableRule(); + + rule.Disable(); + + GuardRule.CanEnable(command, rule); + } + + [Fact] + public void CanDisable_should_throw_exception_if_rule_disabled() + { + var command = new DisableRule(); + + rule.Disable(); + + Assert.Throws(() => GuardRule.CanDisable(command, rule)); + } + + [Fact] + public void CanDisable_should_not_throw_exception_if_rule_enabled() + { + var command = new DisableRule(); + + rule.Enable(); + + GuardRule.CanDisable(command, rule); + } + + [Fact] + public void CanDelete_should_not_throw_exception() + { + var command = new DeleteRule(); + + GuardRule.CanDelete(command); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs new file mode 100644 index 000000000..87cfdec3d --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -0,0 +1,85 @@ +// ========================================================================== +// ContentChangedTriggerTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers +{ + public class ContentChangedTriggerTests + { + private readonly ISchemaProvider schemas = A.Fake(); + + [Fact] + public async Task Should_add_error_if_schemas_ids_are_not_valid() + { + A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) + .Returns(Task.FromResult(null)); + + var trigger = new ContentChangedTrigger + { + Schemas = new List + { + new ContentChangedTriggerSchema() + } + }; + + var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); + + Assert.NotEmpty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_is_null() + { + var trigger = new ContentChangedTrigger(); + + var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_is_empty() + { + var trigger = new ContentChangedTrigger + { + Schemas = new List() + }; + + var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_schemas_ids_are_valid() + { + A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) + .Returns(A.Fake()); + + var trigger = new ContentChangedTrigger + { + Schemas = new List + { + new ContentChangedTriggerSchema() + } + }; + + var errors = await RuleTriggerValidator.ValidateAsync(trigger, schemas); + + Assert.Empty(errors); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs new file mode 100644 index 000000000..87d38c924 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs @@ -0,0 +1,117 @@ +// ========================================================================== +// RuleCommandMiddlewareTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Read.Schemas.Services; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Infrastructure.CQRS.Commands; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Rules +{ + public class RuleCommandMiddlewareTests : HandlerTestBase + { + private readonly ISchemaProvider schemas = A.Fake(); + private readonly RuleCommandMiddleware sut; + private readonly RuleDomainObject rule; + private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); + private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; + private readonly Guid ruleId = Guid.NewGuid(); + + public RuleCommandMiddlewareTests() + { + A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) + .Returns(A.Fake()); + + rule = new RuleDomainObject(ruleId, -1); + + sut = new RuleCommandMiddleware(Handler, schemas); + } + + [Fact] + public async Task Create_should_create_domain_object() + { + var context = CreateContextForCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + + await TestCreate(rule, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task Update_should_update_domain_object() + { + var context = CreateContextForCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction }); + + CreateRule(); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(context); + }); + } + + [Fact] + public async Task Enable_should_update_domain_object() + { + CreateRule(); + DisableRule(); + + var command = CreateContextForCommand(new EnableRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + [Fact] + public async Task Disable_should_update_domain_object() + { + CreateRule(); + + var command = CreateContextForCommand(new DisableRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + [Fact] + public async Task Delete_should_update_domain_object() + { + CreateRule(); + + var command = CreateContextForCommand(new DeleteRule { RuleId = ruleId }); + + await TestUpdate(rule, async _ => + { + await sut.HandleAsync(command); + }); + } + + private void DisableRule() + { + rule.Disable(new DisableRule()); + } + + private void CreateRule() + { + rule.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs new file mode 100644 index 000000000..2bc45e1b3 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs @@ -0,0 +1,240 @@ +// ========================================================================== +// RuleDomainObjectTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +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.Rules; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS; +using Xunit; + +namespace Squidex.Domain.Apps.Write.Rules +{ + public class RuleDomainObjectTests : HandlerTestBase + { + private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); + private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; + private readonly RuleDomainObject sut; + + public Guid RuleId { get; } = Guid.NewGuid(); + + public RuleDomainObjectTests() + { + sut = new RuleDomainObject(RuleId, 0); + } + + [Fact] + public void Create_should_throw_exception_if_created() + { + sut.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + + Assert.Throws(() => + { + sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Create_should_create_events() + { + var command = new CreateRule { Trigger = ruleTrigger, Action = ruleAction }; + + sut.Create(CreateRuleCommand(command)); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleCreated { Trigger = ruleTrigger, Action = ruleAction }) + ); + } + + [Fact] + public void Update_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Update_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Update(CreateRuleCommand(new UpdateRule { Trigger = ruleTrigger, Action = ruleAction })); + }); + } + + [Fact] + public void Update_should_create_events() + { + var newTrigger = new ContentChangedTrigger + { + Schemas = new List() + }; + + var newAction = new WebhookAction + { + Url = new Uri("https://squidex.io/v2") + }; + + CreateRule(); + + var command = new UpdateRule { Trigger = newTrigger, Action = newAction }; + + sut.Update(CreateRuleCommand(command)); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleUpdated { Trigger = newTrigger, Action = newAction }) + ); + } + + [Fact] + public void Enable_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Enable(CreateRuleCommand(new EnableRule())); + }); + } + + [Fact] + public void Enable_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Enable(CreateRuleCommand(new EnableRule())); + }); + } + + [Fact] + public void Enable_should_create_events() + { + CreateRule(); + + var command = new EnableRule(); + + sut.Enable(CreateRuleCommand(command)); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleEnabled()) + ); + } + + [Fact] + public void Disable_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Disable(CreateRuleCommand(new DisableRule())); + }); + } + + [Fact] + public void Disable_should_throw_exception_if_rule_is_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Disable(CreateRuleCommand(new DisableRule())); + }); + } + + [Fact] + public void Disable_should_create_events() + { + CreateRule(); + + var command = new DisableRule(); + + sut.Disable(CreateRuleCommand(command)); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleDisabled()) + ); + } + + [Fact] + public void Delete_should_throw_exception_if_not_created() + { + Assert.Throws(() => + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + }); + } + + [Fact] + public void Delete_should_throw_exception_if_already_deleted() + { + CreateRule(); + DeleteRule(); + + Assert.Throws(() => + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + }); + } + + [Fact] + public void Delete_should_update_create_events() + { + CreateRule(); + + sut.Delete(CreateRuleCommand(new DeleteRule())); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateRuleEvent(new RuleDeleted()) + ); + } + + private void CreateRule() + { + sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); + + ((IAggregate)sut).ClearUncommittedEvents(); + } + + private void DeleteRule() + { + sut.Delete(CreateRuleCommand(new DeleteRule())); + + ((IAggregate)sut).ClearUncommittedEvents(); + } + + protected T CreateRuleEvent(T @event) where T : RuleEvent + { + @event.RuleId = RuleId; + + return CreateEvent(@event); + } + + protected T CreateRuleCommand(T command) where T : RuleAggregateCommand + { + command.RuleId = RuleId; + + return CreateCommand(command); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs deleted file mode 100644 index 567e4a241..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/Guards/GuardWebhookTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -// ========================================================================== -// GuardWebhookTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FakeItEasy; -using Squidex.Domain.Apps.Core.Webhooks; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure; -using Xunit; - -namespace Squidex.Domain.Apps.Write.Webhooks.Guards -{ - public class GuardWebhookTests - { - private readonly ISchemaProvider schemas = A.Fake(); - - public GuardWebhookTests() - { - A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) - .Returns(A.Fake()); - } - - [Fact] - public async Task CanCreate_should_throw_exception_if_url_defined() - { - var command = new CreateWebhook(); - - await Assert.ThrowsAsync(() => GuardWebhook.CanCreate(command, schemas)); - } - - [Fact] - public async Task CanCreate_should_throw_exception_if_url_not_valid() - { - var command = new CreateWebhook { Url = new Uri("/invalid", UriKind.Relative) }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanCreate(command, schemas)); - } - - [Fact] - public async Task CanCreate_should_throw_exception_if_schema_id_not_found() - { - A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) - .Returns(Task.FromResult(null)); - - var command = new CreateWebhook - { - Schemas = new List - { - new WebhookSchema() - }, - Url = new Uri("/invalid", UriKind.Relative) - }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanCreate(command, schemas)); - } - - [Fact] - public async Task CanCreate_should_not_throw_exception_if_schema_id_found() - { - var command = new CreateWebhook - { - Schemas = new List - { - new WebhookSchema() - }, - Url = new Uri("/invalid", UriKind.Relative) - }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanCreate(command, schemas)); - } - - [Fact] - public async Task CanUpdate_should_throw_exception_if_url_not_defined() - { - var command = new UpdateWebhook(); - - await Assert.ThrowsAsync(() => GuardWebhook.CanUpdate(command, schemas)); - } - - [Fact] - public async Task CanUpdate_should_throw_exception_if_url_not_valid() - { - var command = new UpdateWebhook { Url = new Uri("/invalid", UriKind.Relative) }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanUpdate(command, schemas)); - } - - [Fact] - public async Task CanUpdate_should_throw_exception_if_schema_id_not_found() - { - A.CallTo(() => schemas.FindSchemaByIdAsync(A.Ignored, false)) - .Returns(Task.FromResult(null)); - - var command = new UpdateWebhook - { - Schemas = new List - { - new WebhookSchema() - }, - Url = new Uri("/invalid", UriKind.Relative) - }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanUpdate(command, schemas)); - } - - [Fact] - public async Task CanUpdate_should_not_throw_exception_if_schema_id_found() - { - var command = new UpdateWebhook - { - Schemas = new List - { - new WebhookSchema() - }, - Url = new Uri("/invalid", UriKind.Relative) - }; - - await Assert.ThrowsAsync(() => GuardWebhook.CanUpdate(command, schemas)); - } - - [Fact] - public void CanDelete_should_not_throw_exception() - { - var command = new DeleteWebhook(); - - GuardWebhook.CanDelete(command); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs deleted file mode 100644 index 37e786b7d..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookCommandMiddlewareTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -// ========================================================================== -// WebhookCommandMiddlewareTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FakeItEasy; -using Squidex.Domain.Apps.Core.Webhooks; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.Schemas.Services; -using Squidex.Domain.Apps.Write.TestHelpers; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS.Commands; -using Xunit; - -namespace Squidex.Domain.Apps.Write.Webhooks -{ - public class WebhookCommandMiddlewareTests : HandlerTestBase - { - private readonly ISchemaProvider schemas = A.Fake(); - private readonly WebhookCommandMiddleware sut; - private readonly WebhookDomainObject webhook; - private readonly Uri url = new Uri("http://squidex.io"); - private readonly Guid schemaId = Guid.NewGuid(); - private readonly Guid webhookId = Guid.NewGuid(); - private readonly List webhookSchemas; - - public WebhookCommandMiddlewareTests() - { - A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)) - .Returns(A.Fake()); - - webhook = new WebhookDomainObject(webhookId, -1); - - webhookSchemas = new List - { - new WebhookSchema { SchemaId = schemaId } - }; - - sut = new WebhookCommandMiddleware(Handler, schemas); - } - - [Fact] - public async Task Create_should_create_domain_object() - { - var context = CreateContextForCommand(new CreateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); - - await TestCreate(webhook, async _ => - { - await sut.HandleAsync(context); - }); - - A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened(); - } - - [Fact] - public async Task Update_should_update_domain_object() - { - var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); - - A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns(A.Fake()); - - CreateWebhook(); - - await TestUpdate(webhook, async _ => - { - await sut.HandleAsync(context); - }); - - A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).MustHaveHappened(); - } - - [Fact] - public async Task Update_should_throw_exception_when_schema_is_not_found() - { - var context = CreateContextForCommand(new UpdateWebhook { Schemas = webhookSchemas, Url = url, WebhookId = webhookId }); - - A.CallTo(() => schemas.FindSchemaByIdAsync(schemaId, false)).Returns((ISchemaEntity)null); - - CreateWebhook(); - - await Assert.ThrowsAsync(async () => - { - await TestCreate(webhook, async _ => - { - await sut.HandleAsync(context); - }); - }); - } - - [Fact] - public async Task Delete_should_update_domain_object() - { - CreateWebhook(); - - var command = CreateContextForCommand(new DeleteWebhook { WebhookId = webhookId }); - - await TestUpdate(webhook, async _ => - { - await sut.HandleAsync(command); - }); - } - - private void CreateWebhook() - { - webhook.Create(new CreateWebhook { Url = url }); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs deleted file mode 100644 index 182f00d84..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Webhooks/WebhookDomainObjectTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -// ========================================================================== -// WebhookDomainObjectTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Events.Webhooks; -using Squidex.Domain.Apps.Write.TestHelpers; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure; -using Squidex.Infrastructure.CQRS; -using Xunit; - -namespace Squidex.Domain.Apps.Write.Webhooks -{ - public class WebhookDomainObjectTests : HandlerTestBase - { - private readonly Uri url = new Uri("http://squidex.io"); - private readonly WebhookDomainObject sut; - - public Guid WebhookId { get; } = Guid.NewGuid(); - - public WebhookDomainObjectTests() - { - sut = new WebhookDomainObject(WebhookId, 0); - } - - [Fact] - public void Create_should_throw_exception_if_created() - { - sut.Create(new CreateWebhook { Url = url }); - - Assert.Throws(() => - { - sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url })); - }); - } - - [Fact] - public void Create_should_create_events() - { - var command = new CreateWebhook { Url = url }; - - sut.Create(CreateWebhookCommand(command)); - - sut.GetUncomittedEvents() - .ShouldHaveSameEvents( - CreateWebhookEvent(new WebhookCreated - { - Url = url, - Schemas = command.Schemas, - SharedSecret = command.SharedSecret, - WebhookId = command.WebhookId - }) - ); - } - - [Fact] - public void Update_should_throw_exception_if_not_created() - { - Assert.Throws(() => - { - sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url })); - }); - } - - [Fact] - public void Update_should_throw_exception_if_webhook_is_deleted() - { - CreateWebhook(); - DeleteWebhook(); - - Assert.Throws(() => - { - sut.Update(CreateWebhookCommand(new UpdateWebhook { Url = url })); - }); - } - - [Fact] - public void Update_should_create_events() - { - CreateWebhook(); - - var command = new UpdateWebhook { Url = url }; - - sut.Update(CreateWebhookCommand(command)); - - sut.GetUncomittedEvents() - .ShouldHaveSameEvents( - CreateWebhookEvent(new WebhookUpdated { Url = url, Schemas = command.Schemas }) - ); - } - - [Fact] - public void Delete_should_throw_exception_if_not_created() - { - Assert.Throws(() => - { - sut.Delete(CreateWebhookCommand(new DeleteWebhook())); - }); - } - - [Fact] - public void Delete_should_throw_exception_if_already_deleted() - { - CreateWebhook(); - DeleteWebhook(); - - Assert.Throws(() => - { - sut.Delete(CreateWebhookCommand(new DeleteWebhook())); - }); - } - - [Fact] - public void Delete_should_update_properties_create_events() - { - CreateWebhook(); - - sut.Delete(CreateWebhookCommand(new DeleteWebhook())); - - sut.GetUncomittedEvents() - .ShouldHaveSameEvents( - CreateWebhookEvent(new WebhookDeleted()) - ); - } - - private void CreateWebhook() - { - sut.Create(CreateWebhookCommand(new CreateWebhook { Url = url })); - - ((IAggregate)sut).ClearUncommittedEvents(); - } - - private void DeleteWebhook() - { - sut.Delete(CreateWebhookCommand(new DeleteWebhook())); - - ((IAggregate)sut).ClearUncommittedEvents(); - } - - protected T CreateWebhookEvent(T @event) where T : WebhookEvent - { - @event.WebhookId = WebhookId; - - return CreateEvent(@event); - } - - protected T CreateWebhookCommand(T command) where T : WebhookAggregateCommand - { - command.WebhookId = WebhookId; - - return CreateCommand(command); - } - } -}