From 46ee7459ada064dcba8390442819d794284bbcab Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 30 Oct 2017 20:22:59 +0100 Subject: [PATCH] API updated. --- .../HandleRules/RuleService.cs | 3 +- .../Triggers/ContentChangedTriggerHandler.cs | 14 +- .../Rules/MongoRuleEventRepository.cs | 13 +- .../Rules/MongoRuleRepository.cs | 12 +- .../MongoRuleRepository_EventHandling.cs | 18 +- .../Repositories/IRuleEventRepository.cs | 2 + src/Squidex/Config/Domain/ReadModule.cs | 21 +- .../Config/Domain/StoreMongoDbModule.cs | 12 +- src/Squidex/Config/Domain/WriteModule.cs | 6 +- .../Models/Actions/WebhookActionDto.cs} | 25 +- .../Models/Converters/RuleActionDtoFactory.cs | 34 +++ .../Rules/Models/Converters/RuleConverter.cs | 57 ++++ .../Converters/RuleTriggerDtoFactory.cs | 38 +++ .../Models/CreateRuleDto.cs} | 16 +- .../Api/Rules/Models/RuleActionDto.cs | 23 ++ .../Controllers/Api/Rules/Models/RuleDto.cs | 67 +++++ .../Models/RuleEventDto.cs} | 17 +- .../Models/RuleEventsDto.cs} | 12 +- .../Api/Rules/Models/RuleTriggerDto.cs | 23 ++ .../Triggers/ContentChangedTriggerDto.cs | 36 +++ .../ContentChangedTriggerSchemaDto.cs} | 6 +- .../Models/UpdateRuleDto.cs} | 20 +- .../Controllers/Api/Rules/RulesController.cs | 245 ++++++++++++++++++ .../Converters/FieldPropertiesDtoFactory.cs | 1 + .../Api/Schemas/Models/FieldPropertiesDto.cs | 3 +- .../{ => Fields}/AssetsFieldPropertiesDto.cs | 2 +- .../{ => Fields}/BooleanFieldPropertiesDto.cs | 2 +- .../DateTimeFieldPropertiesDto.cs | 2 +- .../GeolocationFieldPropertiesDto.cs | 2 +- .../{ => Fields}/JsonFieldPropertiesDto.cs | 2 +- .../{ => Fields}/NumberFieldPropertiesDto.cs | 2 +- .../ReferencesFieldPropertiesDto.cs | 2 +- .../{ => Fields}/StringFieldPropertiesDto.cs | 2 +- .../{ => Fields}/TagsFieldPropertiesDto.cs | 2 +- .../Api/Webhooks/Models/WebhookDto.cs | 89 ------- .../Api/Webhooks/WebhooksController.cs | 220 ---------------- ...verter.cs => JsonInheritanceConverter2.cs} | 6 +- .../Rules/RuleEnqueuerTests.cs | 102 ++++++++ 38 files changed, 749 insertions(+), 410 deletions(-) rename src/Squidex/Controllers/Api/{Webhooks/Models/WebhookCreatedDto.cs => Rules/Models/Actions/WebhookActionDto.cs} (54%) create mode 100644 src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs create mode 100644 src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs create mode 100644 src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs rename src/Squidex/Controllers/Api/{Webhooks/Models/UpdateWebhookDto.cs => Rules/Models/CreateRuleDto.cs} (60%) create mode 100644 src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs create mode 100644 src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs rename src/Squidex/Controllers/Api/{Webhooks/Models/WebhookEventDto.cs => Rules/Models/RuleEventDto.cs} (79%) rename src/Squidex/Controllers/Api/{Webhooks/Models/WebhookEventsDto.cs => Rules/Models/RuleEventsDto.cs} (65%) create mode 100644 src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs create mode 100644 src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs rename src/Squidex/Controllers/Api/{Webhooks/Models/WebhookSchemaDto.cs => Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs} (87%) rename src/Squidex/Controllers/Api/{Webhooks/Models/CreateWebhookDto.cs => Rules/Models/UpdateRuleDto.cs} (50%) create mode 100644 src/Squidex/Controllers/Api/Rules/RulesController.cs rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/AssetsFieldPropertiesDto.cs (94%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/BooleanFieldPropertiesDto.cs (95%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/DateTimeFieldPropertiesDto.cs (96%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/GeolocationFieldPropertiesDto.cs (95%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/JsonFieldPropertiesDto.cs (92%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/NumberFieldPropertiesDto.cs (96%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/ReferencesFieldPropertiesDto.cs (95%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/StringFieldPropertiesDto.cs (97%) rename src/Squidex/Controllers/Api/Schemas/Models/{ => Fields}/TagsFieldPropertiesDto.cs (94%) delete mode 100644 src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs delete mode 100644 src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs rename src/Squidex/Controllers/{Api/Schemas/Models/Converters/JsonInheritanceConverter.cs => JsonInheritanceConverter2.cs} (95%) create mode 100644 tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index 913ce09d5..35fecb5ac 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -12,7 +12,6 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Events; @@ -114,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules var dumpBuilder = new StringBuilder(result.Dump); dumpBuilder.AppendLine(); - dumpBuilder.AppendFormat("Elapesed {0}.", actionWatch.Elapsed); + dumpBuilder.AppendFormat("Elapsed {0}.", actionWatch.Elapsed); dumpBuilder.AppendLine(); if (result.Exception is TimeoutException || result.Exception is OperationCanceledException) diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs index 5a3c542d4..e0b8ef424 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Triggers/ContentChangedTriggerHandler.cs @@ -32,18 +32,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Triggers return false; } - private static bool MatchsSchema(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event) + private static bool MatchsSchema(ContentChangedTriggerSchema schema, SchemaEvent @event) { - return @event.SchemaId.Id == webhookSchema.SchemaId; + return @event.SchemaId.Id == schema.SchemaId; } - private static bool MatchsType(ContentChangedTriggerSchema webhookSchema, SchemaEvent @event) + private static bool MatchsType(ContentChangedTriggerSchema schema, SchemaEvent @event) { return - (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); + (schema.SendCreate && @event is ContentCreated) || + (schema.SendUpdate && @event is ContentUpdated) || + (schema.SendDelete && @event is ContentDeleted) || + (schema.SendPublish && @event is ContentStatusChanged statusChanged && statusChanged.Status == Status.Published); } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs index 1d0ce4859..d7016942c 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs @@ -48,11 +48,20 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules public async Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20) { - var webhookEventEntities = + var ruleEventEntities = await Collection.Find(x => x.AppId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created) .ToListAsync(); - return webhookEventEntities; + return ruleEventEntities; + } + + public async Task FindAsync(Guid id) + { + var ruleEvent = + await Collection.Find(x => x.Id == id) + .FirstOrDefaultAsync(); + + return ruleEvent; } public async Task CountByAppAsync(Guid appId) diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs index c4dc37beb..378b512f9 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { private static readonly List EmptyRules = new List(); private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1); - private Dictionary> inMemoryWebhooks; + private Dictionary> inMemoryRules; public MongoRuleRepository(IMongoDatabase database) : base(database) @@ -55,20 +55,20 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { await EnsureRulesLoadedAsync(); - return inMemoryWebhooks.GetOrDefault(appId) ?? EmptyRules; + return inMemoryRules.GetOrDefault(appId) ?? EmptyRules; } private async Task EnsureRulesLoadedAsync() { - if (inMemoryWebhooks == null) + if (inMemoryRules == null) { try { await lockObject.WaitAsync(); - if (inMemoryWebhooks == null) + if (inMemoryRules == null) { - inMemoryWebhooks = new Dictionary>(); + inMemoryRules = new Dictionary>(); var webhooks = await Collection.Find(new BsonDocument()) @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules foreach (var webhook in webhooks) { - inMemoryWebhooks.GetOrAddNew(webhook.AppId).Add(webhook); + inMemoryRules.GetOrAddNew(webhook.AppId).Add(webhook); } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs index 4de792863..db2e3db37 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs @@ -41,8 +41,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { w.Rule = RuleEventDispatcher.Create(@event); - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryRules.GetOrAddNew(w.AppId).Add(w); }); } @@ -54,8 +54,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { w.Rule.Apply(@event); - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryRules.GetOrAddNew(w.AppId).Add(w); }); } @@ -67,8 +67,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { w.Rule.Apply(@event); - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryRules.GetOrAddNew(w.AppId).Add(w); }); } @@ -80,8 +80,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { w.Rule.Apply(@event); - inMemoryWebhooks.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); - inMemoryWebhooks.GetOrAddNew(w.AppId).Add(w); + inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id); + inMemoryRules.GetOrAddNew(w.AppId).Add(w); }); } @@ -89,7 +89,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules { await EnsureRulesLoadedAsync(); - inMemoryWebhooks.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId); + inMemoryRules.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/Rules/Repositories/IRuleEventRepository.cs b/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs index b34448f4e..256aa9b71 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs +++ b/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs @@ -31,5 +31,7 @@ namespace Squidex.Domain.Apps.Read.Rules.Repositories Task CountByAppAsync(Guid appId); Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20); + + Task FindAsync(Guid id); } } diff --git a/src/Squidex/Config/Domain/ReadModule.cs b/src/Squidex/Config/Domain/ReadModule.cs index c9cd02801..fca1204f7 100644 --- a/src/Squidex/Config/Domain/ReadModule.cs +++ b/src/Squidex/Config/Domain/ReadModule.cs @@ -11,6 +11,9 @@ using System.Linq; using Autofac; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.HandleRules.ActionHandlers; +using Squidex.Domain.Apps.Core.HandleRules.Triggers; using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Read.Apps.Services.Implementations; @@ -18,10 +21,10 @@ using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Read.Contents.Edm; using Squidex.Domain.Apps.Read.Contents.GraphQL; using Squidex.Domain.Apps.Read.History; +using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas.Services; using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; -using Squidex.Domain.Apps.Read.Webhooks; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; @@ -104,17 +107,27 @@ namespace Squidex.Config.Domain .AsSelf() .InstancePerDependency(); - builder.RegisterType() + builder.RegisterType() .As() .AsSelf() .InstancePerDependency(); - builder.RegisterType() + builder.RegisterType() .As() .AsSelf() .InstancePerDependency(); - builder.RegisterType() + builder.RegisterType() + .As() + .AsSelf() + .SingleInstance(); + + builder.RegisterType() + .As() + .AsSelf() + .SingleInstance(); + + builder.RegisterType() .AsSelf() .SingleInstance(); diff --git a/src/Squidex/Config/Domain/StoreMongoDbModule.cs b/src/Squidex/Config/Domain/StoreMongoDbModule.cs index f232207f3..faa0c355f 100644 --- a/src/Squidex/Config/Domain/StoreMongoDbModule.cs +++ b/src/Squidex/Config/Domain/StoreMongoDbModule.cs @@ -22,11 +22,11 @@ using Squidex.Domain.Apps.Read.MongoDb.Apps; using Squidex.Domain.Apps.Read.MongoDb.Assets; using Squidex.Domain.Apps.Read.MongoDb.Contents; using Squidex.Domain.Apps.Read.MongoDb.History; +using Squidex.Domain.Apps.Read.MongoDb.Rules; using Squidex.Domain.Apps.Read.MongoDb.Schemas; -using Squidex.Domain.Apps.Read.MongoDb.Webhooks; +using Squidex.Domain.Apps.Read.Rules.Repositories; using Squidex.Domain.Apps.Read.Schemas.Repositories; using Squidex.Domain.Apps.Read.Schemas.Services.Implementations; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; using Squidex.Domain.Users; using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb.Infrastructure; @@ -136,9 +136,9 @@ namespace Squidex.Config.Domain .AsSelf() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) - .As() + .As() .As() .AsSelf() .SingleInstance(); @@ -171,9 +171,9 @@ namespace Squidex.Config.Domain .AsSelf() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .WithParameter(ResolvedParameter.ForNamed(MongoDatabaseRegistration)) - .As() + .As() .As() .As() .AsSelf() diff --git a/src/Squidex/Config/Domain/WriteModule.cs b/src/Squidex/Config/Domain/WriteModule.cs index 24389f8e9..f4b7ee25f 100644 --- a/src/Squidex/Config/Domain/WriteModule.cs +++ b/src/Squidex/Config/Domain/WriteModule.cs @@ -13,8 +13,8 @@ using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Write.Apps; using Squidex.Domain.Apps.Write.Assets; using Squidex.Domain.Apps.Write.Contents; +using Squidex.Domain.Apps.Write.Rules; using Squidex.Domain.Apps.Write.Schemas; -using Squidex.Domain.Apps.Write.Webhooks; using Squidex.Infrastructure.CQRS.Commands; using Squidex.Pipeline.CommandMiddlewares; @@ -75,7 +75,7 @@ namespace Squidex.Config.Domain .As() .SingleInstance(); - builder.RegisterType() + builder.RegisterType() .As() .SingleInstance(); @@ -95,7 +95,7 @@ namespace Squidex.Config.Domain .AsSelf() .SingleInstance(); - builder.Register>(c => (id => new WebhookDomainObject(id, -1))) + builder.Register>(c => (id => new RuleDomainObject(id, -1))) .AsSelf() .SingleInstance(); diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs b/src/Squidex/Controllers/Api/Rules/Models/Actions/WebhookActionDto.cs similarity index 54% rename from src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/Actions/WebhookActionDto.cs index f00234562..d7f1c909e 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookCreatedDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/Actions/WebhookActionDto.cs @@ -1,5 +1,5 @@ // ========================================================================== -// WebhookCreatedDto.cs +// WebhookActionDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -8,25 +8,30 @@ using System; using System.ComponentModel.DataAnnotations; +using NJsonSchema.Annotations; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models.Actions { - public sealed class WebhookCreatedDto + [JsonSchema("Webhook")] + public sealed class WebhookActionDto : RuleActionDto { /// - /// The id of the webhook. + /// The url of the rule. /// - public Guid Id { get; set; } + [Required] + public Uri Url { get; set; } /// /// The shared secret that is used to calculate the signature. /// - [Required] public string SharedSecret { get; set; } - /// - /// The version of the schema. - /// - public long Version { get; set; } + public override RuleAction ToAction() + { + return SimpleMapper.Map(this, new WebhookAction()); + } } } diff --git a/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs new file mode 100644 index 000000000..fe28cd931 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleActionDtoFactory.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// RuleActionDtoFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Controllers.Api.Rules.Models.Actions; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Controllers.Api.Rules.Models.Converters +{ + public sealed class RuleActionDtoFactory : IRuleActionVisitor + { + private static readonly RuleActionDtoFactory Instance = new RuleActionDtoFactory(); + + private RuleActionDtoFactory() + { + } + + public static RuleActionDto Create(RuleAction properties) + { + return properties.Accept(Instance); + } + + public RuleActionDto Visit(WebhookAction action) + { + return SimpleMapper.Map(action, new WebhookActionDto()); + } + } +} diff --git a/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs new file mode 100644 index 000000000..6d4a3b54d --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleConverter.cs @@ -0,0 +1,57 @@ +// ========================================================================== +// RuleConverter.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Controllers.Api.Rules.Models.Converters +{ + public static class RuleConverter + { + public static RuleDto ToModel(this IRuleEntity entity) + { + var dto = new RuleDto(); + + SimpleMapper.Map(entity, dto); + SimpleMapper.Map(entity.Rule, dto); + + if (entity.Rule.Trigger != null) + { + dto.Trigger = RuleTriggerDtoFactory.Create(entity.Rule.Trigger); + } + + if (entity.Rule.Action != null) + { + dto.Action = RuleActionDtoFactory.Create(entity.Rule.Action); + } + + return dto; + } + + public static UpdateRule ToCommand(this UpdateRuleDto dto) + { + var command = new UpdateRule + { + Trigger = dto.Trigger?.ToTrigger(), Action = dto.Action?.ToAction() + }; + + return command; + } + + public static CreateRule ToCommand(this CreateRuleDto dto) + { + var command = new CreateRule + { + Trigger = dto.Trigger.ToTrigger(), Action = dto.Action.ToAction() + }; + + return command; + } + } +} diff --git a/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs new file mode 100644 index 000000000..a9d38a6bf --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/Converters/RuleTriggerDtoFactory.cs @@ -0,0 +1,38 @@ +// ========================================================================== +// RuleTriggerDtoFactory.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Linq; +using Squidex.Controllers.Api.Rules.Models.Triggers; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Controllers.Api.Rules.Models.Converters +{ + public sealed class RuleTriggerDtoFactory : IRuleTriggerVisitor + { + private static readonly RuleTriggerDtoFactory Instance = new RuleTriggerDtoFactory(); + + private RuleTriggerDtoFactory() + { + } + + public static RuleTriggerDto Create(RuleTrigger properties) + { + return properties.Accept(Instance); + } + + public RuleTriggerDto Visit(ContentChangedTrigger trigger) + { + return new ContentChangedTriggerDto + { + Schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToList() + }; + } + } +} diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs b/src/Squidex/Controllers/Api/Rules/Models/CreateRuleDto.cs similarity index 60% rename from src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/CreateRuleDto.cs index d5b7da231..060ff6461 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/UpdateWebhookDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/CreateRuleDto.cs @@ -1,29 +1,27 @@ // ========================================================================== -// UpdateWebhookDto.cs +// CreateRuleDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models { - public sealed class UpdateWebhookDto + public sealed class CreateRuleDto { /// - /// The url of the webhook. + /// The trigger properties. /// [Required] - public Uri Url { get; set; } + public RuleTriggerDto Trigger { get; set; } /// - /// The schema settings. + /// The action properties. /// [Required] - public List Schemas { get; set; } + public RuleActionDto Action { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs b/src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs new file mode 100644 index 000000000..e2bc33439 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/RuleActionDto.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// RuleActionDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Runtime.Serialization; +using Newtonsoft.Json; +using NJsonSchema.Converters; +using Squidex.Controllers.Api.Rules.Models.Actions; +using Squidex.Domain.Apps.Core.Rules; + +namespace Squidex.Controllers.Api.Rules.Models +{ + [JsonConverter(typeof(JsonInheritanceConverter), "actionType")] + [KnownType(typeof(WebhookActionDto))] + public abstract class RuleActionDto + { + public abstract RuleAction ToAction(); + } +} diff --git a/src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs b/src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs new file mode 100644 index 000000000..d6c3ade48 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/RuleDto.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// RuleDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; +using NodaTime; +using Squidex.Infrastructure; + +namespace Squidex.Controllers.Api.Rules.Models +{ + public sealed class RuleDto + { + /// + /// The id of the rule. + /// + public Guid Id { get; set; } + + /// + /// The user that has created the rule. + /// + [Required] + public RefToken CreatedBy { get; set; } + + /// + /// The user that has updated the rule. + /// + [Required] + public RefToken LastModifiedBy { get; set; } + + /// + /// The date and time when the rule has been created. + /// + public Instant Created { get; set; } + + /// + /// The date and time when the rule has been modified last. + /// + public Instant LastModified { get; set; } + + /// + /// The version of the rule. + /// + public int Version { get; set; } + + /// + /// The trigger properties. + /// + [Required] + public RuleTriggerDto Trigger { get; set; } + + /// + /// The action properties. + /// + [Required] + public RuleActionDto Action { get; set; } + + /// + /// Determines if the rule is enabled. + /// + public bool IsEnabled { get; set; } + } +} diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs b/src/Squidex/Controllers/Api/Rules/Models/RuleEventDto.cs similarity index 79% rename from src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/RuleEventDto.cs index 4473fe749..5513d5a25 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/RuleEventDto.cs @@ -1,5 +1,5 @@ // ========================================================================== -// WebhookEventDto.cs +// RuleEventDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,11 +9,12 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; -using Squidex.Domain.Apps.Read.Webhooks; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Read.Rules; -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models { - public sealed class WebhookEventDto + public sealed class RuleEventDto { /// /// The id of the event. @@ -26,10 +27,10 @@ namespace Squidex.Controllers.Api.Webhooks.Models public Instant Created { get; set; } /// - /// The request url. + /// The description /// [Required] - public Uri RequestUrl { get; set; } + public string Description { get; set; } /// /// The name of the event. @@ -55,11 +56,11 @@ namespace Squidex.Controllers.Api.Webhooks.Models /// /// The result of the event. /// - public WebhookResult Result { get; set; } + public RuleResult Result { get; set; } /// /// The result of the job. /// - public WebhookJobResult JobResult { get; set; } + public RuleJobResult JobResult { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs b/src/Squidex/Controllers/Api/Rules/Models/RuleEventsDto.cs similarity index 65% rename from src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/RuleEventsDto.cs index 977f44c04..f0cda8a38 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookEventsDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/RuleEventsDto.cs @@ -1,23 +1,23 @@ // ========================================================================== -// WebhookEventsDto.cs +// RuleEventsDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models { - public sealed class WebhookEventsDto + public sealed class RuleEventsDto { /// - /// The total number of webhook events. + /// The total number of rule events. /// public long Total { get; set; } /// - /// The webhook events. + /// The rule events. /// - public WebhookEventDto[] Items { get; set; } + public RuleEventDto[] Items { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs b/src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs new file mode 100644 index 000000000..3d009cd50 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/RuleTriggerDto.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// RuleTriggerDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Runtime.Serialization; +using Newtonsoft.Json; +using NJsonSchema.Converters; +using Squidex.Controllers.Api.Rules.Models.Triggers; +using Squidex.Domain.Apps.Core.Rules; + +namespace Squidex.Controllers.Api.Rules.Models +{ + [JsonConverter(typeof(JsonInheritanceConverter), "triggerType")] + [KnownType(typeof(ContentChangedTriggerDto))] + public abstract class RuleTriggerDto + { + public abstract RuleTrigger ToTrigger(); + } +} diff --git a/src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs b/src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs new file mode 100644 index 000000000..05af3cf26 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerDto.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// ContentChangedTriggerDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using NJsonSchema.Annotations; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Controllers.Api.Rules.Models.Triggers +{ + [JsonSchema("ContentChanged")] + public sealed class ContentChangedTriggerDto : RuleTriggerDto + { + /// + /// The schema settings. + /// + [Required] + public List Schemas { get; set; } + + public override RuleTrigger ToTrigger() + { + return new ContentChangedTrigger + { + Schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToList() + }; + } + } +} diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs b/src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs similarity index 87% rename from src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs index 251afb1e6..29e0c71fd 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookSchemaDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/Triggers/ContentChangedTriggerSchemaDto.cs @@ -1,5 +1,5 @@ // ========================================================================== -// WebhookSchemaDto.cs +// ContentChangedTriggerSchemaDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -8,9 +8,9 @@ using System; -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models.Triggers { - public sealed class WebhookSchemaDto + public sealed class ContentChangedTriggerSchemaDto { /// /// The id of the schema. diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs b/src/Squidex/Controllers/Api/Rules/Models/UpdateRuleDto.cs similarity index 50% rename from src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs rename to src/Squidex/Controllers/Api/Rules/Models/UpdateRuleDto.cs index d7dcd7f46..bad710cc8 100644 --- a/src/Squidex/Controllers/Api/Webhooks/Models/CreateWebhookDto.cs +++ b/src/Squidex/Controllers/Api/Rules/Models/UpdateRuleDto.cs @@ -1,29 +1,23 @@ // ========================================================================== -// CreateWebhookDto.cs +// UpdateRuleDto.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace Squidex.Controllers.Api.Webhooks.Models +namespace Squidex.Controllers.Api.Rules.Models { - public sealed class CreateWebhookDto + public sealed class UpdateRuleDto { /// - /// The url of the webhook. + /// The trigger properties. /// - [Required] - public Uri Url { get; set; } + public RuleTriggerDto Trigger { get; set; } /// - /// The schema settings. + /// The action properties. /// - [Required] - public List Schemas { get; set; } + public RuleActionDto Action { get; set; } } } diff --git a/src/Squidex/Controllers/Api/Rules/RulesController.cs b/src/Squidex/Controllers/Api/Rules/RulesController.cs new file mode 100644 index 000000000..7c05ac840 --- /dev/null +++ b/src/Squidex/Controllers/Api/Rules/RulesController.cs @@ -0,0 +1,245 @@ +// ========================================================================== +// RulesController.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using NodaTime; +using NSwag.Annotations; +using Squidex.Controllers.Api.Rules.Models; +using Squidex.Controllers.Api.Rules.Models.Converters; +using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Infrastructure.CQRS.Commands; +using Squidex.Infrastructure.Reflection; +using Squidex.Pipeline; + +namespace Squidex.Controllers.Api.Rules +{ + /// + /// Manages and retrieves information about schemas. + /// + [ApiAuthorize] + [ApiExceptionFilter] + [AppApi] + [SwaggerTag(nameof(Rules))] + [MustBeAppDeveloper] + public sealed class RulesController : ControllerBase + { + private readonly IRuleRepository rulesRepository; + private readonly IRuleEventRepository ruleEventsRepository; + + public RulesController(ICommandBus commandBus, + IRuleRepository rulesRepository, + IRuleEventRepository ruleEventsRepository) + : base(commandBus) + { + this.rulesRepository = rulesRepository; + this.ruleEventsRepository = ruleEventsRepository; + } + + /// + /// Get rules. + /// + /// The name of the app. + /// + /// 200 => Rules returned. + /// 404 => App not found. + /// + [HttpGet] + [Route("apps/{app}/rules/")] + [ProducesResponseType(typeof(RuleDto[]), 200)] + [ApiCosts(1)] + public async Task GetRules(string app) + { + var rules = await rulesRepository.QueryByAppAsync(App.Id); + + var response = rules.Select(r => r.ToModel()); + + return Ok(response); + } + + /// + /// Create a new rule. + /// + /// The name of the app. + /// The rule object that needs to be added to the app. + /// + /// 201 => Rule created. + /// 400 => Rule is not valid. + /// 404 => App not found. + /// + [HttpPost] + [Route("apps/{app}/rules/")] + [ProducesResponseType(typeof(EntityCreatedDto), 201)] + [ProducesResponseType(typeof(ErrorDto), 400)] + [ApiCosts(1)] + public async Task PostRule(string app, [FromBody] CreateRuleDto request) + { + var command = request.ToCommand(); + + var context = await CommandBus.PublishAsync(command); + + var result = context.Result>(); + var response = new EntityCreatedDto { Id = result.IdOrValue.ToString(), Version = result.Version }; + + return CreatedAtAction(nameof(GetRules), new { app }, response); + } + + /// + /// Update a rule. + /// + /// The name of the app. + /// The id of the rule to update. + /// The rule object that needs to be added to the app. + /// + /// 204 => Rule updated. + /// 400 => Rule is not valid. + /// 404 => Rule or app not found. + /// + /// + /// All events for the specified schemas will be sent to the url. The timeout is 2 seconds. + /// + [HttpPut] + [Route("apps/{app}/rules/{id}/")] + [ProducesResponseType(typeof(ErrorDto), 400)] + [ApiCosts(1)] + public async Task PutRule(string app, Guid id, [FromBody] UpdateRuleDto request) + { + var command = request.ToCommand(); + + await CommandBus.PublishAsync(command); + + return NoContent(); + } + + /// + /// Enable a rule. + /// + /// The name of the app. + /// The id of the rule to enable. + /// + /// 204 => Rule enabled. + /// 400 => Rule already enabled. + /// 404 => Rule or app not found. + /// + [HttpPut] + [Route("apps/{app}/rules/{id}/enable/")] + [ApiCosts(1)] + public async Task EnableRule(string app, Guid id) + { + await CommandBus.PublishAsync(new EnableRule { RuleId = id }); + + return NoContent(); + } + + /// + /// Disable a rule. + /// + /// The name of the app. + /// The id of the rule to disable. + /// + /// 204 => Rule disabled. + /// 400 => Rule already disabled. + /// 404 => Rule or app not found. + /// + [HttpPut] + [Route("apps/{app}/rules/{id}/disable/")] + [ApiCosts(1)] + public async Task DisableRule(string app, Guid id) + { + await CommandBus.PublishAsync(new DisableRule { RuleId = id }); + + return NoContent(); + } + + /// + /// Delete a rule. + /// + /// The name of the app. + /// The id of the rule to delete. + /// + /// 204 => Rule has been deleted. + /// 404 => Rule or app not found. + /// + [HttpDelete] + [Route("apps/{app}/rules/{id}/")] + [ApiCosts(1)] + public async Task DeleteRule(string app, Guid id) + { + await CommandBus.PublishAsync(new DeleteRule { RuleId = id }); + + return NoContent(); + } + + /// + /// Get rule events. + /// + /// The name of the app. + /// The number of events to skip. + /// The number of events to take. + /// + /// 200 => Rule events returned. + /// 404 => App not found. + /// + [HttpGet] + [Route("apps/{app}/rules/events/")] + [ProducesResponseType(typeof(RuleEventsDto), 200)] + [ApiCosts(0)] + public async Task GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20) + { + var taskForItems = ruleEventsRepository.QueryByAppAsync(App.Id, skip, take); + var taskForCount = ruleEventsRepository.CountByAppAsync(App.Id); + + await Task.WhenAll(taskForItems, taskForCount); + + var response = new RuleEventsDto + { + Total = taskForCount.Result, + Items = taskForItems.Result.Select(x => + { + var itemModel = new RuleEventDto(); + + SimpleMapper.Map(x, itemModel); + SimpleMapper.Map(x.Job, itemModel); + + return itemModel; + }).ToArray() + }; + + return Ok(response); + } + + /// + /// Enqueue the event to be send. + /// + /// The name of the app. + /// The event to enqueue. + /// + /// 200 => Rule enqueued. + /// 404 => App or rule event not found. + /// + [HttpPut] + [Route("apps/{app}/rules/events/{id}/")] + [ApiCosts(0)] + public async Task PutEvent(string app, Guid id) + { + var entity = await ruleEventsRepository.FindAsync(id); + + if (entity == null) + { + return NotFound(); + } + + await ruleEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant()); + + return Ok(); + } + } +} diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs b/src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs index 12db361af..5b803f707 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs @@ -7,6 +7,7 @@ // ========================================================================== using System.Linq; +using Squidex.Controllers.Api.Schemas.Models.Fields; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs index 4271dd057..f7ac054e7 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/FieldPropertiesDto.cs @@ -9,7 +9,8 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Newtonsoft.Json; -using Squidex.Controllers.Api.Schemas.Models.Converters; +using NJsonSchema.Converters; +using Squidex.Controllers.Api.Schemas.Models.Fields; using Squidex.Domain.Apps.Core.Schemas; namespace Squidex.Controllers.Api.Schemas.Models diff --git a/src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs similarity index 94% rename from src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs index 1becd319c..b2e7a3d50 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/AssetsFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs @@ -10,7 +10,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Assets")] public sealed class AssetsFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/BooleanFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs similarity index 95% rename from src/Squidex/Controllers/Api/Schemas/Models/BooleanFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs index 82c5c081d..8516d4b92 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/BooleanFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Boolean")] public sealed class BooleanFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/DateTimeFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs similarity index 96% rename from src/Squidex/Controllers/Api/Schemas/Models/DateTimeFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs index 2c2c68b13..4cdc78c49 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/DateTimeFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs @@ -13,7 +13,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("DateTime")] public sealed class DateTimeFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/GeolocationFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs similarity index 95% rename from src/Squidex/Controllers/Api/Schemas/Models/GeolocationFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs index 4ad558c5c..76e1a1ce0 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/GeolocationFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Geolocation")] public sealed class GeolocationFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/JsonFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/JsonFieldPropertiesDto.cs similarity index 92% rename from src/Squidex/Controllers/Api/Schemas/Models/JsonFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/JsonFieldPropertiesDto.cs index 2957c8cce..68f9fe290 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/JsonFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/JsonFieldPropertiesDto.cs @@ -10,7 +10,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Json")] public sealed class JsonFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberFieldPropertiesDto.cs similarity index 96% rename from src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberFieldPropertiesDto.cs index 3bd914770..ff5b8fc14 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/NumberFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/NumberFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Number")] public sealed class NumberFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/ReferencesFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs similarity index 95% rename from src/Squidex/Controllers/Api/Schemas/Models/ReferencesFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs index 38fbcb992..e35541086 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/ReferencesFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/ReferencesFieldPropertiesDto.cs @@ -11,7 +11,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("References")] public sealed class ReferencesFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringFieldPropertiesDto.cs similarity index 97% rename from src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/StringFieldPropertiesDto.cs index 32a3e00da..ebf41e4e7 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/StringFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/StringFieldPropertiesDto.cs @@ -12,7 +12,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("String")] public sealed class StringFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Schemas/Models/TagsFieldPropertiesDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/Fields/TagsFieldPropertiesDto.cs similarity index 94% rename from src/Squidex/Controllers/Api/Schemas/Models/TagsFieldPropertiesDto.cs rename to src/Squidex/Controllers/Api/Schemas/Models/Fields/TagsFieldPropertiesDto.cs index 45e91f5f0..0769967ad 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/TagsFieldPropertiesDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/Fields/TagsFieldPropertiesDto.cs @@ -10,7 +10,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; -namespace Squidex.Controllers.Api.Schemas.Models +namespace Squidex.Controllers.Api.Schemas.Models.Fields { [JsonSchema("Tags")] public sealed class TagsFieldPropertiesDto : FieldPropertiesDto diff --git a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs b/src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs deleted file mode 100644 index 29bb7670e..000000000 --- a/src/Squidex/Controllers/Api/Webhooks/Models/WebhookDto.cs +++ /dev/null @@ -1,89 +0,0 @@ -// ========================================================================== -// WebhookDto.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using NodaTime; -using Squidex.Infrastructure; - -namespace Squidex.Controllers.Api.Webhooks.Models -{ - public sealed class WebhookDto - { - /// - /// The id of the webhook. - /// - public Guid Id { get; set; } - - /// - /// The user that has created the webhook. - /// - [Required] - public RefToken CreatedBy { get; set; } - - /// - /// The user that has updated the webhook. - /// - [Required] - public RefToken LastModifiedBy { get; set; } - - /// - /// The date and time when the webhook has been created. - /// - public Instant Created { get; set; } - - /// - /// The date and time when the webhook has been modified last. - /// - public Instant LastModified { get; set; } - - /// - /// The version of the webhook. - /// - public int Version { get; set; } - - /// - /// The number of succceeded calls. - /// - public long TotalSucceeded { get; set; } - - /// - /// The number of failed calls. - /// - public long TotalFailed { get; set; } - - /// - /// The number of timedout calls. - /// - public long TotalTimedout { get; set; } - - /// - /// The average response time in milliseconds. - /// - public long AverageRequestTimeMs { get; set; } - - /// - /// The url of the webhook. - /// - [Required] - public Uri Url { get; set; } - - /// - /// The shared secret that is used to calculate the signature. - /// - [Required] - public string SharedSecret { get; set; } - - /// - /// The schema settings. - /// - [Required] - public List Schemas { get; set; } - } -} diff --git a/src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs b/src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs deleted file mode 100644 index e672333b5..000000000 --- a/src/Squidex/Controllers/Api/Webhooks/WebhooksController.cs +++ /dev/null @@ -1,220 +0,0 @@ -// ========================================================================== -// WebhooksController.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using NodaTime; -using NSwag.Annotations; -using Squidex.Controllers.Api.Webhooks.Models; -using Squidex.Domain.Apps.Core.Webhooks; -using Squidex.Domain.Apps.Read.Webhooks.Repositories; -using Squidex.Domain.Apps.Write.Webhooks.Commands; -using Squidex.Infrastructure.CQRS.Commands; -using Squidex.Infrastructure.Reflection; -using Squidex.Pipeline; - -namespace Squidex.Controllers.Api.Webhooks -{ - /// - /// Manages and retrieves information about schemas. - /// - [ApiAuthorize] - [ApiExceptionFilter] - [AppApi] - [SwaggerTag(nameof(Webhooks))] - [MustBeAppDeveloper] - public sealed class WebhooksController : ControllerBase - { - private readonly IWebhookRepository webhooksRepository; - private readonly IWebhookEventRepository webhookEventsRepository; - - public WebhooksController(ICommandBus commandBus, - IWebhookRepository webhooksRepository, - IWebhookEventRepository webhookEventsRepository) - : base(commandBus) - { - this.webhooksRepository = webhooksRepository; - this.webhookEventsRepository = webhookEventsRepository; - } - - /// - /// Get webhooks. - /// - /// The name of the app. - /// - /// 200 => Webhooks returned. - /// 404 => App not found. - /// - [HttpGet] - [Route("apps/{app}/webhooks/")] - [ProducesResponseType(typeof(WebhookDto[]), 200)] - [ApiCosts(1)] - public async Task GetWebhooks(string app) - { - var webhooks = await webhooksRepository.QueryByAppAsync(App.Id); - - var response = webhooks.Select(w => - { - var totalCount = w.TotalTimedout + w.TotalSucceeded + w.TotalFailed; - var totalAverage = totalCount == 0 ? 0 : w.TotalRequestTime / totalCount; - - var schemas = w.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchemaDto())).ToList(); - - return SimpleMapper.Map(w, new WebhookDto { AverageRequestTimeMs = totalAverage, Schemas = schemas }); - }); - - return Ok(response); - } - - /// - /// Create a new webhook. - /// - /// The name of the app. - /// The webhook object that needs to be added to the app. - /// - /// 201 => Webhook created. - /// 400 => Webhook is not valid. - /// 404 => App not found. - /// - /// - /// All events for the specified schemas will be sent to the url. The timeout is 2 seconds. - /// - [HttpPost] - [Route("apps/{app}/webhooks/")] - [ProducesResponseType(typeof(EntityCreatedDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] - [ApiCosts(1)] - public async Task PostWebhook(string app, [FromBody] CreateWebhookDto request) - { - var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList(); - - var command = new CreateWebhook { Url = request.Url, Schemas = schemas }; - - var context = await CommandBus.PublishAsync(command); - - var result = context.Result>(); - var response = new WebhookCreatedDto { Id = result.IdOrValue, SharedSecret = command.SharedSecret, Version = result.Version }; - - return CreatedAtAction(nameof(GetWebhooks), new { app }, response); - } - - /// - /// Update a webhook. - /// - /// The name of the app. - /// The id of the webhook to update. - /// The webhook object that needs to be added to the app. - /// - /// 203 => Webhook updated. - /// 400 => Webhook is not valid. - /// 404 => Webhook or app not found. - /// - /// - /// All events for the specified schemas will be sent to the url. The timeout is 2 seconds. - /// - [HttpPut] - [Route("apps/{app}/webhooks/{id}/")] - [ProducesResponseType(typeof(ErrorDto), 400)] - [ApiCosts(1)] - public async Task PutWebhook(string app, Guid id, [FromBody] CreateWebhookDto request) - { - var schemas = request.Schemas.Select(s => SimpleMapper.Map(s, new WebhookSchema())).ToList(); - - var command = new UpdateWebhook { WebhookId = id, Url = request.Url, Schemas = schemas }; - - await CommandBus.PublishAsync(command); - - return NoContent(); - } - - /// - /// Delete a webhook. - /// - /// The name of the app. - /// The id of the webhook to delete. - /// - /// 204 => Webhook has been deleted. - /// 404 => Webhook or app not found. - /// - [HttpDelete] - [Route("apps/{app}/webhooks/{id}/")] - [ApiCosts(1)] - public async Task DeleteWebhook(string app, Guid id) - { - await CommandBus.PublishAsync(new DeleteWebhook { WebhookId = id }); - - return NoContent(); - } - - /// - /// Get webhook events. - /// - /// The name of the app. - /// The number of events to skip. - /// The number of events to take. - /// - /// 200 => Webhook events returned. - /// 404 => App not found. - /// - [HttpGet] - [Route("apps/{app}/webhooks/events/")] - [ProducesResponseType(typeof(WebhookEventsDto), 200)] - [ApiCosts(0)] - public async Task GetEvents(string app, [FromQuery] int skip = 0, [FromQuery] int take = 20) - { - var taskForItems = webhookEventsRepository.QueryByAppAsync(App.Id, skip, take); - var taskForCount = webhookEventsRepository.CountByAppAsync(App.Id); - - await Task.WhenAll(taskForItems, taskForCount); - - var response = new WebhookEventsDto - { - Total = taskForCount.Result, - Items = taskForItems.Result.Select(x => - { - var itemModel = new WebhookEventDto(); - - SimpleMapper.Map(x, itemModel); - SimpleMapper.Map(x.Job, itemModel); - - return itemModel; - }).ToArray() - }; - - return Ok(response); - } - - /// - /// Enqueue the event to be send. - /// - /// The name of the app. - /// The event to enqueue. - /// - /// 200 => Webhook enqueued. - /// 404 => App or webhook event not found. - /// - [HttpPut] - [Route("apps/{app}/webhooks/events/{id}/")] - [ApiCosts(0)] - public async Task PutEvent(string app, Guid id) - { - var entity = await webhookEventsRepository.FindAsync(id); - - if (entity == null) - { - return NotFound(); - } - - await webhookEventsRepository.EnqueueAsync(id, SystemClock.Instance.GetCurrentInstant()); - - return Ok(); - } - } -} diff --git a/src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs b/src/Squidex/Controllers/JsonInheritanceConverter2.cs similarity index 95% rename from src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs rename to src/Squidex/Controllers/JsonInheritanceConverter2.cs index e55080547..5cd6ac724 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/Converters/JsonInheritanceConverter.cs +++ b/src/Squidex/Controllers/JsonInheritanceConverter2.cs @@ -16,9 +16,9 @@ using NJsonSchema.Annotations; #pragma warning disable SA1306 // Field names must begin with lower-case letter -namespace Squidex.Controllers.Api.Schemas.Models.Converters +namespace Squidex.Controllers { - public sealed class JsonInheritanceConverter : JsonConverter + public sealed class JsonInheritanceConverter2 : JsonConverter { private readonly string discriminator; @@ -54,7 +54,7 @@ namespace Squidex.Controllers.Api.Schemas.Models.Converters } } - public JsonInheritanceConverter(string discriminator) + public JsonInheritanceConverter2(string discriminator) { this.discriminator = discriminator; } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs b/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs new file mode 100644 index 000000000..500bc2a44 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs @@ -0,0 +1,102 @@ +// ========================================================================== +// RuleEnqueuerTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FakeItEasy; +using NodaTime; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Rules.Actions; +using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Infrastructure; +using Squidex.Infrastructure.CQRS.Events; +using Xunit; + +namespace Squidex.Domain.Apps.Read.Rules +{ + public class RuleEnqueuerTests + { + private readonly IRuleRepository ruleRepository = A.Fake(); + private readonly IRuleEventRepository ruleEventRepository = A.Fake(); + private readonly RuleService ruleService = A.Fake(); + private readonly Instant now = SystemClock.Instance.GetCurrentInstant(); + private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); + private readonly RuleEnqueuer sut; + + public RuleEnqueuerTests() + { + sut = new RuleEnqueuer( + ruleEventRepository, + ruleRepository, + ruleService); + } + + [Fact] + public void Should_return_contents_filter_for_events_filter() + { + Assert.Equal(".*", sut.EventsFilter); + } + + [Fact] + public void Should_return_type_name_for_name() + { + Assert.Equal(typeof(RuleEnqueuer).Name, sut.Name); + } + + [Fact] + public Task Should_do_nothing_on_clear() + { + return sut.ClearAsync(); + } + + [Fact] + public async Task Should_update_repositories_on_with_jobs_from_sender() + { + var @event = Envelope.Create(new ContentCreated { AppId = appId }); + + var rule1 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); + var rule2 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); + var rule3 = new Rule(new ContentChangedTrigger(), new WebhookAction { Url = new Uri("https://squidex.io") }); + + var job1 = new RuleJob { Created = now }; + var job2 = new RuleJob { Created = now }; + + var ruleEntity1 = A.Fake(); + var ruleEntity2 = A.Fake(); + var ruleEntity3 = A.Fake(); + + A.CallTo(() => ruleEntity1.Rule).Returns(rule1); + A.CallTo(() => ruleEntity2.Rule).Returns(rule2); + A.CallTo(() => ruleEntity3.Rule).Returns(rule3); + + A.CallTo(() => ruleRepository.QueryCachedByAppAsync(appId.Id)) + .Returns(new List { ruleEntity1, ruleEntity2, ruleEntity3 }); + + A.CallTo(() => ruleService.CreateJob(rule1, @event)) + .Returns(job1); + + A.CallTo(() => ruleService.CreateJob(rule2, @event)) + .Returns(job2); + + A.CallTo(() => ruleService.CreateJob(rule3, @event)) + .Returns(null); + + await sut.On(@event); + + A.CallTo(() => ruleEventRepository.EnqueueAsync(job1, now)) + .MustHaveHappened(); + + A.CallTo(() => ruleEventRepository.EnqueueAsync(job2, now)) + .MustHaveHappened(); + } + } +} \ No newline at end of file