From c48a58fa90c814188b859d18b1dd4e92f09e0880 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Feb 2019 16:22:24 +0100 Subject: [PATCH 01/55] Started with first refactoring. --- .../Actions/Algolia/AlgoliaAction.cs | 3 +- .../Actions/Algolia/AlgoliaPlugin.cs | 22 ++++++ .../Actions/AzureQueue/AzureQueueAction.cs | 1 + .../Actions/AzureQueue/AzureQueuePlugin.cs | 22 ++++++ .../Actions/Discourse/DiscourseAction.cs | 1 + .../Actions/Discourse/DiscoursePlugin.cs | 22 ++++++ .../ElasticSearch/ElasticSearchAction.cs | 1 + .../ElasticSearch/ElasticSearchPlugin.cs | 22 ++++++ .../Actions/Email/EmailAction.cs | 1 + .../Actions/Email/EmailPlugin.cs | 22 ++++++ .../Actions/Fastly/FastlyAction.cs | 1 + .../Actions/Fastly/FastlyPlugin.cs | 22 ++++++ .../Actions/Medium/MediumAction.cs | 1 + .../Actions/Medium/MediumPlugin.cs | 22 ++++++ .../Actions/Prerender/PrerenderAction.cs | 1 + .../Actions/Prerender/PrerenderPlugin.cs | 22 ++++++ .../Actions/Slack/SlackAction.cs | 1 + .../Actions/Slack/SlackPlugin.cs | 22 ++++++ .../Actions/TriggerTypes.cs | 57 -------------- .../Actions/Twitter/TweetAction.cs | 1 + .../Actions/Twitter/TwitterPlugin.cs | 22 ++++++ .../Actions/Webhook/WebhookAction.cs | 1 + .../Actions/Webhook/WebhookPlugin.cs | 22 ++++++ .../HandleRules/FormattableAttribute.cs | 16 ++++ .../HandleRules}/RuleActionAttribute.cs | 2 +- .../HandleRules/RuleActionDefinition.cs | 4 +- .../RuleActionHandlerAttribute.cs | 3 +- .../HandleRules/RuleActionRegistry.cs | 71 ++++++++++------- .../ImageSharpAssetThumbnailGenerator.cs | 4 +- .../Configuration/ConfigurationExtensions.cs | 65 ++++++++++++++++ .../Configuration}/Options.cs | 4 +- .../DependencyInjectionExtensions.cs} | 70 ++++------------- src/Squidex.Infrastructure/Plugins/IPlugin.cs | 18 +++++ .../Plugins/IWebPlugin.cs | 16 ++++ .../Plugins/PluginManager.cs | 59 ++++++++++++++ .../Plugins/PluginOptions.cs | 14 ++++ .../Squidex.Infrastructure.csproj | 1 + .../Translations/DeepLTranslator.cs | 16 ++-- .../Translations/DeepLTranslatorOptions.cs | 14 ++++ .../Api/Config/Swagger/SwaggerServices.cs | 2 +- .../Rules/Models/RuleActionConverter.cs | 3 +- .../Rules/Models/RuleActionProcessor.cs | 3 +- .../Api/Controllers/Rules/RulesController.cs | 26 +------ src/Squidex/Config/Domain/AssetServices.cs | 2 + src/Squidex/Config/Domain/EntitiesServices.cs | 2 + .../Config/Domain/EventPublishersServices.cs | 1 + .../Config/Domain/EventStoreServices.cs | 2 + .../Config/Domain/InfrastructureServices.cs | 18 +---- src/Squidex/Config/Domain/LoggingServices.cs | 1 + src/Squidex/Config/Domain/RuleServices.cs | 3 +- .../Config/Domain/SerializationServices.cs | 1 - src/Squidex/Config/Domain/StoreServices.cs | 2 + .../Config/Domain/SubscriptionServices.cs | 1 + src/Squidex/Config/Orleans/OrleansServices.cs | 1 + src/Squidex/Config/Web/WebServices.cs | 1 + .../Pipeline/Plugins/PluginExtensions.cs | 76 +++++++++++++++++++ src/Squidex/Squidex.csproj | 1 + src/Squidex/WebStartup.cs | 3 + 58 files changed, 641 insertions(+), 197 deletions(-) create mode 100644 extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs delete mode 100644 extensions/Squidex.Extensions/Actions/TriggerTypes.cs create mode 100644 extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs create mode 100644 extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/FormattableAttribute.cs rename {extensions/Squidex.Extensions/Actions => src/Squidex.Domain.Apps.Core.Operations/HandleRules}/RuleActionAttribute.cs (94%) rename extensions/Squidex.Extensions/Actions/RuleElement.cs => src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs (88%) rename {extensions/Squidex.Extensions/Actions => src/Squidex.Domain.Apps.Core.Operations/HandleRules}/RuleActionHandlerAttribute.cs (92%) rename extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs => src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs (55%) create mode 100644 src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs rename src/{Squidex/Config => Squidex.Infrastructure/Configuration}/Options.cs (84%) rename src/{Squidex/Config/ServiceExtensions.cs => Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs} (61%) create mode 100644 src/Squidex.Infrastructure/Plugins/IPlugin.cs create mode 100644 src/Squidex.Infrastructure/Plugins/IWebPlugin.cs create mode 100644 src/Squidex.Infrastructure/Plugins/PluginManager.cs create mode 100644 src/Squidex.Infrastructure/Plugins/PluginOptions.cs create mode 100644 src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs create mode 100644 src/Squidex/Pipeline/Plugins/PluginExtensions.cs diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index baff5dc43..cef0f0aba 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Algolia @@ -28,7 +29,7 @@ namespace Squidex.Extensions.Actions.Algolia public string ApiKey { get; set; } [Required] - [Display(Name = "Index Name", Description = "THe name of the index.")] + [Display(Name = "Index Name", Description = "The name of the index.")] public string IndexName { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs new file mode 100644 index 000000000..71d0f4c08 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Algolia +{ + public sealed class AlgoliaPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index 0ee5de663..2acc7ec7c 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.RegularExpressions; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs new file mode 100644 index 000000000..e069c08d2 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.AzureQueue +{ + public sealed class AzureQueuePlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index 4711dec26..a73f1f493 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs new file mode 100644 index 000000000..2d7d9f6d7 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Discourse +{ + public sealed class DiscoursePlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 620f9ef6e..2bf262e82 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs new file mode 100644 index 000000000..49cf06697 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.ElasticSearch +{ + public sealed class ElasticSearchPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index 0de1cb24f..067e5fbce 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Email diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs b/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs new file mode 100644 index 000000000..34a5c88ce --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Email +{ + public sealed class EmailPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs index 928dab189..d1840782d 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Fastly diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs new file mode 100644 index 000000000..8268d912a --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Fastly +{ + public sealed class FastlyPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 1363aa430..6bbf1bb30 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Medium diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs new file mode 100644 index 000000000..8a1090672 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Medium +{ + public sealed class MediumPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index 47c37eafa..4672a8d61 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Prerender diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs new file mode 100644 index 000000000..89e1a6c9b --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Prerender +{ + public sealed class PrerenderPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index 792a0f3fc..4e2863f8a 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs new file mode 100644 index 000000000..1149a5875 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Slack +{ + public sealed class SlackPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/TriggerTypes.cs b/extensions/Squidex.Extensions/Actions/TriggerTypes.cs deleted file mode 100644 index 731a280f9..000000000 --- a/extensions/Squidex.Extensions/Actions/TriggerTypes.cs +++ /dev/null @@ -1,57 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Infrastructure; - -namespace Squidex.Extensions.Actions -{ - public static class TriggerTypes - { - private const string TriggerSuffix = "Trigger"; - private const string TriggerSuffixV2 = "TriggerV2"; - - public static readonly IReadOnlyDictionary All = new Dictionary - { - [GetTriggerName(typeof(ContentChangedTriggerV2))] = new RuleElement - { - IconImage = "", - IconColor = "#3389ff", - Display = "Content changed", - Description = "For content changes like created, updated, published, unpublished..." - }, - [GetTriggerName(typeof(AssetChangedTriggerV2))] = new RuleElement - { - IconImage = "", - IconColor = "#3389ff", - Display = "Asset changed", - Description = "For asset changes like uploaded, updated (reuploaded), renamed, deleted..." - }, - [GetTriggerName(typeof(SchemaChangedTrigger))] = new RuleElement - { - IconImage = "", - IconColor = "#3389ff", - Display = "Schema changed", - Description = "When a schema definition has been created, updated, published or deleted..." - }, - [GetTriggerName(typeof(UsageTrigger))] = new RuleElement - { - IconImage = "", - IconColor = "#3389ff", - Display = "Usage exceeded", - Description = "When monthly API calls exceed a specified limit for one time a month..." - } - }; - - private static string GetTriggerName(Type type) - { - return type.TypeName(false, TriggerSuffix, TriggerSuffixV2); - } - } -} diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index a16f98c6f..cc0fa3d17 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Twitter diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs new file mode 100644 index 000000000..a5b120f4a --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Twitter +{ + public sealed class TwitterPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index e2c260b11..0877c37c9 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs new file mode 100644 index 000000000..c9786cdf0 --- /dev/null +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Actions.Webhook +{ + public sealed class WebhookPlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + RuleActionRegistry.Add(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/FormattableAttribute.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/FormattableAttribute.cs new file mode 100644 index 000000000..296dcb840 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/FormattableAttribute.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; + +namespace Squidex.Domain.Apps.Core.HandleRules +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class FormattableAttribute : Attribute + { + } +} diff --git a/extensions/Squidex.Extensions/Actions/RuleActionAttribute.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionAttribute.cs similarity index 94% rename from extensions/Squidex.Extensions/Actions/RuleActionAttribute.cs rename to src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionAttribute.cs index c5fa0343f..31c1f2368 100644 --- a/extensions/Squidex.Extensions/Actions/RuleActionAttribute.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionAttribute.cs @@ -7,7 +7,7 @@ using System; -namespace Squidex.Extensions.Actions +namespace Squidex.Domain.Apps.Core.HandleRules { [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class RuleActionAttribute : Attribute diff --git a/extensions/Squidex.Extensions/Actions/RuleElement.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs similarity index 88% rename from extensions/Squidex.Extensions/Actions/RuleElement.cs rename to src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs index 6d7244c01..595f6d56b 100644 --- a/extensions/Squidex.Extensions/Actions/RuleElement.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs @@ -7,9 +7,9 @@ using System; -namespace Squidex.Extensions.Actions +namespace Squidex.Domain.Apps.Core.HandleRules { - public sealed class RuleElement + public sealed class RuleActionDefinition { public Type Type { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/RuleActionHandlerAttribute.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs similarity index 92% rename from extensions/Squidex.Extensions/Actions/RuleActionHandlerAttribute.cs rename to src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs index 5da96ebf5..24d29ccea 100644 --- a/extensions/Squidex.Extensions/Actions/RuleActionHandlerAttribute.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs @@ -6,10 +6,9 @@ // ========================================================================== using System; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure; -namespace Squidex.Extensions.Actions +namespace Squidex.Domain.Apps.Core.HandleRules { [AttributeUsage(AttributeTargets.Class, Inherited = false)] public sealed class RuleActionHandlerAttribute : Attribute diff --git a/extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs similarity index 55% rename from extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs rename to src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs index dac6d42dc..a17a58872 100644 --- a/extensions/Squidex.Extensions/Actions/RuleElementRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs @@ -12,21 +12,16 @@ using System.Reflection; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; -namespace Squidex.Extensions.Actions +namespace Squidex.Domain.Apps.Core.HandleRules { - public static class RuleElementRegistry + public static class RuleActionRegistry { private const string ActionSuffix = "Action"; private const string ActionSuffixV2 = "Action"; private static readonly HashSet ActionHandlerTypes = new HashSet(); - private static readonly Dictionary ActionTypes = new Dictionary(); + private static readonly Dictionary ActionTypes = new Dictionary(); - public static IReadOnlyDictionary Triggers - { - get { return TriggerTypes.All; } - } - - public static IReadOnlyDictionary Actions + public static IReadOnlyDictionary Actions { get { return ActionTypes; } } @@ -36,10 +31,10 @@ namespace Squidex.Extensions.Actions get { return ActionHandlerTypes; } } - static RuleElementRegistry() + static RuleActionRegistry() { var actionTypes = - typeof(RuleElementRegistry).Assembly + typeof(RuleActionRegistry).Assembly .GetTypes() .Where(x => typeof(RuleAction).IsAssignableFrom(x)) .Where(x => x.GetCustomAttribute() != null) @@ -48,25 +43,47 @@ namespace Squidex.Extensions.Actions foreach (var actionType in actionTypes) { - var name = GetActionName(actionType); - - var metadata = actionType.GetCustomAttribute(); - - ActionTypes[name] = - new RuleElement - { - Type = actionType, - Display = metadata.Display, - Description = metadata.Description, - IconColor = metadata.IconColor, - IconImage = metadata.IconImage, - ReadMore = metadata.ReadMore - }; - - ActionHandlerTypes.Add(actionType.GetCustomAttribute().HandlerType); + Add(actionType); } } + public static void Add() where T : RuleAction + { + Add(typeof(T)); + } + + private static void Add(Type actionType) + { + var metadata = actionType.GetCustomAttribute(); + + if (metadata == null) + { + return; + } + + var handlerAttribute = actionType.GetCustomAttribute(); + + if (handlerAttribute == null) + { + return; + } + + var name = GetActionName(actionType); + + ActionTypes[name] = + new RuleActionDefinition + { + Type = actionType, + Display = metadata.Display, + Description = metadata.Description, + IconColor = metadata.IconColor, + IconImage = metadata.IconImage, + ReadMore = metadata.ReadMore + }; + + ActionHandlerTypes.Add(actionType.GetCustomAttribute().HandlerType); + } + public static TypeNameRegistry MapRuleActions(this TypeNameRegistry typeNameRegistry) { foreach (var actionType in ActionTypes.Values) diff --git a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index f36089eeb..82c4e2dc9 100644 --- a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -19,8 +19,8 @@ namespace Squidex.Infrastructure.Assets.ImageSharp { public ImageSharpAssetThumbnailGenerator() { - Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Jpeg); - Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Png); + SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Jpeg); + SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Png); } public Task CreateThumbnailAsync(Stream source, Stream destination, int? width, int? height, string mode) diff --git a/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs new file mode 100644 index 000000000..2194f753b --- /dev/null +++ b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Globalization; +using System.Linq; +using Microsoft.Extensions.Configuration; + +namespace Squidex.Infrastructure.Configuration +{ + public static class ConfigurationExtensions + { + public static T GetOptionalValue(this IConfiguration config, string path, T defaultValue = default) + { + var value = config.GetValue(path, defaultValue); + + return value; + } + + public static int GetOptionalValue(this IConfiguration config, string path, int defaultValue) + { + var value = config.GetValue(path); + + if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) + { + result = defaultValue; + } + + return result; + } + + public static string GetRequiredValue(this IConfiguration config, string path) + { + var value = config.GetValue(path); + + if (string.IsNullOrWhiteSpace(value)) + { + var name = string.Join(" ", path.Split(':').Select(x => x.ToPascalCase())); + + throw new ConfigurationException($"Configure the {name} with '{path}'."); + } + + return value; + } + + public static string ConfigureByOption(this IConfiguration config, string path, Options options) + { + var value = config.GetRequiredValue(path); + + if (options.TryGetValue(value, out var action)) + { + action(); + } + else + { + throw new ConfigurationException($"Unsupported value '{value}' for '{path}', supported: {string.Join(" ", options.Keys)}."); + } + + return value; + } + } +} diff --git a/src/Squidex/Config/Options.cs b/src/Squidex.Infrastructure/Configuration/Options.cs similarity index 84% rename from src/Squidex/Config/Options.cs rename to src/Squidex.Infrastructure/Configuration/Options.cs index 1962bba5e..79a3cdfae 100644 --- a/src/Squidex/Config/Options.cs +++ b/src/Squidex.Infrastructure/Configuration/Options.cs @@ -1,14 +1,14 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; using System.Collections.Generic; -namespace Squidex.Config +namespace Squidex.Infrastructure.Configuration { public sealed class Options : Dictionary { diff --git a/src/Squidex/Config/ServiceExtensions.cs b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs similarity index 61% rename from src/Squidex/Config/ServiceExtensions.cs rename to src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs index 8d185bf60..5b39ad14b 100644 --- a/src/Squidex/Config/ServiceExtensions.cs +++ b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs @@ -1,20 +1,18 @@ // ========================================================================== // Squidex Headless CMS // ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) +// Copyright (c) Squidex UG (haftungsbeschraenkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== using System; -using System.Globalization; using System.Linq; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure; +using Microsoft.Extensions.DependencyInjection.Extensions; -namespace Squidex.Config +namespace Squidex.Infrastructure.DependencyInjection { - public static class ServiceExtensions + public static class DependencyInjectionExtensions { public sealed class InterfaceRegistrator { @@ -30,6 +28,16 @@ namespace Squidex.Config return this; } + public InterfaceRegistrator AsOptional() + { + if (typeof(TInterface) != typeof(T)) + { + services.TryAddSingleton(typeof(TInterface), c => c.GetRequiredService()); + } + + return this; + } + public InterfaceRegistrator As() { if (typeof(TInterface) != typeof(T)) @@ -89,55 +97,5 @@ namespace Squidex.Config services.AddSingleton(typeof(IInitializable), c => c.GetRequiredService()); } } - - public static T GetOptionalValue(this IConfiguration config, string path, T defaultValue = default) - { - var value = config.GetValue(path, defaultValue); - - return value; - } - - public static int GetOptionalValue(this IConfiguration config, string path, int defaultValue) - { - var value = config.GetValue(path); - var result = defaultValue; - - if (string.IsNullOrWhiteSpace(value) || !int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) - { - result = defaultValue; - } - - return result; - } - - public static string GetRequiredValue(this IConfiguration config, string path) - { - var value = config.GetValue(path); - - if (string.IsNullOrWhiteSpace(value)) - { - var name = string.Join(' ', path.Split(':').Select(x => x.ToPascalCase())); - - throw new ConfigurationException($"Configure the {name} with '{path}'."); - } - - return value; - } - - public static string ConfigureByOption(this IConfiguration config, string path, Options options) - { - var value = config.GetRequiredValue(path); - - if (options.TryGetValue(value, out var action)) - { - action(); - } - else - { - throw new ConfigurationException($"Unsupported value '{value}' for '{path}', supported: {string.Join(' ', options.Keys)}."); - } - - return value; - } } } diff --git a/src/Squidex.Infrastructure/Plugins/IPlugin.cs b/src/Squidex.Infrastructure/Plugins/IPlugin.cs new file mode 100644 index 000000000..72ed11047 --- /dev/null +++ b/src/Squidex.Infrastructure/Plugins/IPlugin.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Squidex.Infrastructure.Plugins +{ + public interface IPlugin + { + void ConfigureServices(IServiceCollection services, IConfiguration configuration); + } +} diff --git a/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs new file mode 100644 index 000000000..2baddf066 --- /dev/null +++ b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Microsoft.AspNetCore.Builder; + +namespace Squidex.Infrastructure.Plugins +{ + public interface IWebPlugin + { + void Configure(IApplicationBuilder app); + } +} diff --git a/src/Squidex.Infrastructure/Plugins/PluginManager.cs b/src/Squidex.Infrastructure/Plugins/PluginManager.cs new file mode 100644 index 000000000..08e2381ce --- /dev/null +++ b/src/Squidex.Infrastructure/Plugins/PluginManager.cs @@ -0,0 +1,59 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Squidex.Infrastructure.Plugins +{ + public sealed class PluginManager + { + private readonly HashSet plugins = new HashSet(); + + public void Add(Assembly assembly) + { + Guard.NotNull(assembly, nameof(assembly)); + + var pluginTypes = + assembly.GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract); + + foreach (var pluginType in pluginTypes) + { + var plugin = (IPlugin)Activator.CreateInstance(pluginType); + + plugins.Add(plugin); + } + } + + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + Guard.NotNull(services, nameof(services)); + Guard.NotNull(configuration, nameof(configuration)); + + foreach (var plugin in plugins) + { + plugin.ConfigureServices(services, configuration); + } + } + + public void Configure(IApplicationBuilder app) + { + Guard.NotNull(app, nameof(app)); + + foreach (var plugin in plugins.OfType()) + { + plugin.Configure(app); + } + } + } +} diff --git a/src/Squidex.Infrastructure/Plugins/PluginOptions.cs b/src/Squidex.Infrastructure/Plugins/PluginOptions.cs new file mode 100644 index 000000000..561e0845a --- /dev/null +++ b/src/Squidex.Infrastructure/Plugins/PluginOptions.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Plugins +{ + public sealed class PluginOptions + { + public string[] Plugins { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index ea5c0a8b0..f411a7f4c 100644 --- a/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -8,6 +8,7 @@ True + diff --git a/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs b/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs index 877e76d7a..09920e6b6 100644 --- a/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs +++ b/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs @@ -10,6 +10,7 @@ using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; using Squidex.Infrastructure.Json; namespace Squidex.Infrastructure.Translations @@ -18,7 +19,7 @@ namespace Squidex.Infrastructure.Translations { private const string Url = "https://api.deepl.com/v2/translate"; private readonly HttpClient httpClient = new HttpClient(); - private readonly string authKey; + private readonly DeepLTranslatorOptions deepLOptions; private readonly IJsonSerializer jsonSerializer; private sealed class Response @@ -31,12 +32,12 @@ namespace Squidex.Infrastructure.Translations public string Text { get; set; } } - public DeepLTranslator(string authKey, IJsonSerializer jsonSerializer) + public DeepLTranslator(IOptions deepLOptions, IJsonSerializer jsonSerializer) { - Guard.NotNull(authKey, nameof(authKey)); + Guard.NotNull(deepLOptions, nameof(deepLOptions)); Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); - this.authKey = authKey; + this.deepLOptions = deepLOptions.Value; this.jsonSerializer = jsonSerializer; } @@ -48,9 +49,14 @@ namespace Squidex.Infrastructure.Translations return new Translation(TranslationResult.NotTranslated, sourceText); } + if (string.IsNullOrWhiteSpace(deepLOptions.AuthKey)) + { + return new Translation(TranslationResult.NotImplemented); + } + var parameters = new Dictionary { - ["auth_key"] = authKey, + ["auth_key"] = deepLOptions.AuthKey, ["text"] = sourceText, ["target_lang"] = GetLanguageCode(targetLanguage) }; diff --git a/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs b/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs new file mode 100644 index 000000000..d7124e343 --- /dev/null +++ b/src/Squidex.Infrastructure/Translations/DeepLTranslatorOptions.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Translations +{ + public sealed class DeepLTranslatorOptions + { + public string AuthKey { get; set; } + } +} diff --git a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs index 5059cb21c..07a973ea4 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs @@ -14,8 +14,8 @@ using NSwag.SwaggerGeneration; using NSwag.SwaggerGeneration.Processors; using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.Rules.Models; -using Squidex.Config; using Squidex.Infrastructure; +using Squidex.Infrastructure.DependencyInjection; namespace Squidex.Areas.Api.Config.Swagger { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs index 4efcfb7ad..fe5666dad 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Extensions.Actions; @@ -15,7 +16,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models { public sealed class RuleActionConverter : MyJsonInheritanceConverter { - private static readonly Dictionary Mapping = RuleElementRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type); + private static readonly Dictionary Mapping = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type); public RuleActionConverter() : base("actionType", Mapping) diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs index a0d23e12b..2ea9995b8 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using NJsonSchema; using NSwag.SwaggerGeneration.Processors; using NSwag.SwaggerGeneration.Processors.Contexts; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Extensions.Actions; @@ -36,7 +37,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models Type = JsonObjectType.String, IsRequired = true }; - foreach (var derived in RuleElementRegistry.Actions) + foreach (var derived in RuleActionRegistry.Actions) { var derivedSchema = await context.SchemaGenerator.GenerateAsync(derived.Value.Type, context.SchemaResolver); diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 998f08a70..752b4e041 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using NodaTime; using Squidex.Areas.Api.Controllers.Rules.Models; +using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Repositories; @@ -31,8 +32,7 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiExplorerSettings(GroupName = nameof(Rules))] public sealed class RulesController : ApiController { - private static readonly string RuleActionsEtag = string.Join(";", RuleElementRegistry.Actions.Select(x => x.Key)).Sha256Base64(); - private static readonly string RuleTriggersEtag = string.Join(";", RuleElementRegistry.Triggers.Select(x => x.Key)).Sha256Base64(); + private static readonly string RuleActionsEtag = string.Join(";", RuleActionRegistry.Actions.Select(x => x.Key)).Sha256Base64(); private readonly IAppProvider appProvider; private readonly IRuleEventRepository ruleEventsRepository; @@ -58,33 +58,13 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(0)] public IActionResult GetActions() { - var response = RuleElementRegistry.Actions.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto())); + var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto())); Response.Headers[HeaderNames.ETag] = RuleActionsEtag; return Ok(response); } - /// - /// Get the supported rule triggers. - /// - /// - /// 200 => Rule triggers returned. - /// - [HttpGet] - [Route("rules/triggers/")] - [ProducesResponseType(typeof(Dictionary), 200)] - [ApiPermission] - [ApiCosts(0)] - public IActionResult GetTriggers() - { - var response = RuleElementRegistry.Triggers.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto())); - - Response.Headers[HeaderNames.ETag] = RuleTriggersEtag; - - return Ok(response); - } - /// /// Get rules. /// diff --git a/src/Squidex/Config/Domain/AssetServices.cs b/src/Squidex/Config/Domain/AssetServices.cs index 8ec712b17..d1edf5d54 100644 --- a/src/Squidex/Config/Domain/AssetServices.cs +++ b/src/Squidex/Config/Domain/AssetServices.cs @@ -12,6 +12,8 @@ using MongoDB.Driver.GridFS; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets.ImageSharp; +using Squidex.Infrastructure.Configuration; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Log; namespace Squidex.Config.Domain diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 98641c63f..8cd35728e 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -42,6 +42,8 @@ using Squidex.Domain.Apps.Entities.Schemas.Indexes; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Configuration; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Migrations; using Squidex.Pipeline; diff --git a/src/Squidex/Config/Domain/EventPublishersServices.cs b/src/Squidex/Config/Domain/EventPublishersServices.cs index dad6cd6ec..82004c1db 100644 --- a/src/Squidex/Config/Domain/EventPublishersServices.cs +++ b/src/Squidex/Config/Domain/EventPublishersServices.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; diff --git a/src/Squidex/Config/Domain/EventStoreServices.cs b/src/Squidex/Config/Domain/EventStoreServices.cs index defcbc2c7..03cb235e2 100644 --- a/src/Squidex/Config/Domain/EventStoreServices.cs +++ b/src/Squidex/Config/Domain/EventStoreServices.cs @@ -11,6 +11,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Squidex.Infrastructure; +using Squidex.Infrastructure.Configuration; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Grains; diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index 33687222d..a623e3650 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -18,8 +18,8 @@ using Squidex.Domain.Apps.Entities.Apps.Diagnostics; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.UsageTracking; using Squidex.Shared.Users; @@ -32,19 +32,6 @@ namespace Squidex.Config.Domain { public static void AddMyInfrastructureServices(this IServiceCollection services, IConfiguration config) { - var deeplAuthKey = config.GetValue("translations:deeplAuthKey"); - - if (!string.IsNullOrWhiteSpace(deeplAuthKey)) - { - services.AddSingletonAs(c => new DeepLTranslator(deeplAuthKey, c.GetRequiredService())) - .As(); - } - else - { - services.AddSingletonAs() - .As(); - } - services.AddHealthChecks() .AddCheck("GC", tags: new[] { "node" }) .AddCheck("Orleans", tags: new[] { "cluster" }) @@ -59,6 +46,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .AsSelf(); + services.AddSingletonAs() + .As(); + services.AddSingletonAs(c => new CachingUsageTracker(c.GetRequiredService(), c.GetRequiredService())) .As(); diff --git a/src/Squidex/Config/Domain/LoggingServices.cs b/src/Squidex/Config/Domain/LoggingServices.cs index 5e18bd491..92885e424 100644 --- a/src/Squidex/Config/Domain/LoggingServices.cs +++ b/src/Squidex/Config/Domain/LoggingServices.cs @@ -9,6 +9,7 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Log; using Squidex.Pipeline; diff --git a/src/Squidex/Config/Domain/RuleServices.cs b/src/Squidex/Config/Domain/RuleServices.cs index 02f36ecc3..5bdc3f45d 100644 --- a/src/Squidex/Config/Domain/RuleServices.cs +++ b/src/Squidex/Config/Domain/RuleServices.cs @@ -13,6 +13,7 @@ using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Extensions.Actions; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Config.Domain @@ -45,7 +46,7 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .AsSelf(); - foreach (var actionHandler in RuleElementRegistry.ActionHandlers) + foreach (var actionHandler in RuleActionRegistry.ActionHandlers) { services.AddSingleton(typeof(IRuleActionHandler), actionHandler); } diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index 04646016c..4ef371ba4 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Core.Rules.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; -using Squidex.Extensions.Actions; using Squidex.Infrastructure; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft; diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index 15a41a4d7..8d79f0ec5 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -27,6 +27,8 @@ using Squidex.Domain.Users; using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Infrastructure; +using Squidex.Infrastructure.Configuration; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; diff --git a/src/Squidex/Config/Domain/SubscriptionServices.cs b/src/Squidex/Config/Domain/SubscriptionServices.cs index 10437c2b7..f9d6ca1a1 100644 --- a/src/Squidex/Config/Domain/SubscriptionServices.cs +++ b/src/Squidex/Config/Domain/SubscriptionServices.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; using Squidex.Domain.Users; using Squidex.Infrastructure; +using Squidex.Infrastructure.DependencyInjection; namespace Squidex.Config.Domain { diff --git a/src/Squidex/Config/Orleans/OrleansServices.cs b/src/Squidex/Config/Orleans/OrleansServices.cs index aed630ded..957a3cda5 100644 --- a/src/Squidex/Config/Orleans/OrleansServices.cs +++ b/src/Squidex/Config/Orleans/OrleansServices.cs @@ -15,6 +15,7 @@ using Orleans.Hosting; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; +using Squidex.Infrastructure.Configuration; using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.Orleans; diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 3de6a6f33..19c375aa0 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.DependencyInjection; using Squidex.Config.Domain; +using Squidex.Infrastructure.DependencyInjection; using Squidex.Pipeline; using Squidex.Pipeline.Robots; diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs new file mode 100644 index 000000000..94de2338e --- /dev/null +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -0,0 +1,76 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Reflection; +using McMaster.NETCore.Plugins; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Pipeline.Plugins +{ + public static class PluginExtensions + { + private static readonly Type[] SharedTypes = { typeof(IPlugin) }; + + public static void AddPlugins(IMvcBuilder mvcBuilder, IConfiguration configuration) + { + var options = configuration.Get(); + + if (options.Plugins != null) + { + var pluginManager = new PluginManager(); + + foreach (var pluginPath in options.Plugins) + { + PluginLoader plugin = null; + + if (pluginPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + plugin = PluginLoader.CreateFromAssemblyFile(pluginPath, SharedTypes); + } + else + { + plugin = PluginLoader.CreateFromConfigFile(pluginPath, SharedTypes); + } + + if (plugin != null) + { + var pluginAssembly = plugin.LoadDefaultAssembly(); + + AddParts(mvcBuilder, pluginAssembly); + + var relatedAssemblies = pluginAssembly.GetCustomAttributes(); + + foreach (var relatedAssembly in relatedAssemblies) + { + var assembly = plugin.LoadAssembly(relatedAssembly.AssemblyFileName); + + AddParts(mvcBuilder, assembly); + } + + pluginManager.Add(pluginAssembly); + } + } + + mvcBuilder.Services.AddSingleton(pluginManager); + } + } + + private static void AddParts(IMvcBuilder mvcBuilder, Assembly assembly) + { + var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); + + foreach (var part in partFactory.GetApplicationParts(assembly)) + { + mvcBuilder.PartManager.ApplicationParts.Add(part); + } + } + } +} diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 93a7fd8f6..0cab6d9a6 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -68,6 +68,7 @@ + diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index b68fffec1..61dd0a38d 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -29,6 +29,7 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Extensions.Actions.Twitter; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Diagnostics; +using Squidex.Infrastructure.Translations; using Squidex.Pipeline; using Squidex.Pipeline.Robots; @@ -72,6 +73,8 @@ namespace Squidex config.GetSection("contents")); services.Configure( config.GetSection("assets")); + services.Configure( + config.GetSection("translations:deepL")); services.Configure( config.GetSection("mode")); services.Configure( From 8ca8e0ae9ab8fa31f66e8f72acf59afea4132192 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Feb 2019 17:19:19 +0100 Subject: [PATCH 02/55] Annotate field properties. --- .../Actions/Algolia/AlgoliaAction.cs | 6 +++ .../Actions/AzureQueue/AzureQueueAction.cs | 4 ++ .../Actions/Discourse/DiscourseAction.cs | 11 ++++- .../ElasticSearch/ElasticSearchAction.cs | 9 ++++ .../Actions/Email/EmailAction.cs | 13 +++++ .../Actions/Fastly/FastlyAction.cs | 2 + .../Actions/Medium/MediumAction.cs | 10 ++++ .../Actions/Prerender/PrerenderAction.cs | 3 ++ .../Actions/Slack/SlackAction.cs | 4 ++ .../Actions/Twitter/TweetAction.cs | 3 ++ .../Actions/Webhook/WebhookAction.cs | 5 +- .../Squidex.Extensions/SquidexExtensions.cs | 16 ++++++ src/Squidex/Config/Web/WebServices.cs | 8 ++- .../Pipeline/Plugins/PluginExtensions.cs | 22 +++++++-- src/Squidex/WebStartup.cs | 5 +- .../pages/rules/rule-element.component.html | 12 ++++- .../pages/rules/rule-element.component.scss | 9 ++++ .../rules/pages/rules/rules-page.component.ts | 8 +-- .../app/shared/services/rules.service.spec.ts | 41 +--------------- .../app/shared/services/rules.service.ts | 49 +++++++++++-------- 20 files changed, 162 insertions(+), 78 deletions(-) create mode 100644 extensions/Squidex.Extensions/SquidexExtensions.cs diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index cef0f0aba..c6952b152 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -22,14 +22,20 @@ namespace Squidex.Extensions.Actions.Algolia { [Required] [Display(Name = "Application Id", Description = "The application ID.")] + [DataType(DataType.Text)] + [Formattable] public string AppId { get; set; } [Required] [Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")] + [DataType(DataType.Text)] + [Formattable] public string ApiKey { get; set; } [Required] [Display(Name = "Index Name", Description = "The name of the index.")] + [DataType(DataType.Text)] + [Formattable] public string IndexName { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index 2acc7ec7c..45f176d9f 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs @@ -25,10 +25,14 @@ namespace Squidex.Extensions.Actions.AzureQueue { [Required] [Display(Name = "Connection String", Description = "The connection string to the storage account.")] + [DataType(DataType.Text)] + [Formattable] public string ConnectionString { get; set; } [Required] [Display(Name = "Queue", Description = "The name of the queue.")] + [DataType(DataType.Text)] + [Formattable] public string Queue { get; set; } protected override IEnumerable CustomValidate() diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index a73f1f493..97f6b6538 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs @@ -24,28 +24,37 @@ namespace Squidex.Extensions.Actions.Discourse { [AbsoluteUrl] [Required] - [Display(Name = "Url", Description = "he url to the discourse server.")] + [Display(Name = "Url", Description = "The url to the discourse server.")] + [DataType(DataType.Url)] public Uri Url { get; set; } [Required] [Display(Name = "Api Key", Description = "The api key to authenticate to your discourse server.")] + [DataType(DataType.Text)] public string ApiKey { get; set; } [Required] [Display(Name = "Api Username", Description = "The api username to authenticate to your discourse server.")] + [DataType(DataType.Text)] public string ApiUsername { get; set; } [Required] [Display(Name = "Text", Description = "The text as markdown.")] + [DataType(DataType.MultilineText)] + [Formattable] public string Text { get; set; } [Display(Name = "Title", Description = "The optional title when creating new topics.")] + [DataType(DataType.Text)] + [Formattable] public string Title { get; set; } [Display(Name = "Topic", Description = "The optional topic id.")] + [DataType(DataType.Custom)] public int? Topic { get; set; } [Display(Name = "Category", Description = "The optional category id.")] + [DataType(DataType.Custom)] public int? Category { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 2bf262e82..44f0ce8c2 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -25,20 +25,29 @@ namespace Squidex.Extensions.Actions.ElasticSearch [AbsoluteUrl] [Required] [Display(Name = "Host", Description = "The hostname of the elastic search instance or cluster.")] + [DataType(DataType.Url)] public Uri Host { get; set; } [Required] [Display(Name = "Index Name", Description = "The name of the index.")] + [DataType(DataType.Text)] + [Formattable] public string IndexName { get; set; } [Required] [Display(Name = "Index Type", Description = "The name of the index type.")] + [DataType(DataType.Text)] + [Formattable] public string IndexType { get; set; } [Display(Name = "Username", Description = "The optional username.")] + [DataType(DataType.Text)] + [Formattable] public string Username { get; set; } [Display(Name = "Password", Description = "The optional password.")] + [DataType(DataType.Password)] + [Formattable] public string Password { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index 067e5fbce..be24d3166 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs @@ -22,38 +22,51 @@ namespace Squidex.Extensions.Actions.Email { [Required] [Display(Name = "ServerHost", Description = "The IP address or host to the SMTP server.")] + [DataType(DataType.Text)] public string ServerHost { get; set; } [Required] [Display(Name = "ServerPort", Description = "The port to the SMTP server.")] + [DataType(DataType.Custom)] public int ServerPort { get; set; } [Required] [Display(Name = "ServerUseSsl", Description = "Specify whether the SMPT client uses Secure Sockets Layer (SSL) to encrypt the connection.")] + [DataType(DataType.Custom)] public bool ServerUseSsl { get; set; } [Required] [Display(Name = "ServerUsername", Description = "The username for the SMTP server.")] + [DataType(DataType.Text)] public string ServerUsername { get; set; } [Required] [Display(Name = "ServerPassword", Description = "The password for the SMTP server.")] + [DataType(DataType.Password)] public string ServerPassword { get; set; } [Required] [Display(Name = "MessageFrom", Description = "The email sending address.")] + [DataType(DataType.Text)] + [Formattable] public string MessageFrom { get; set; } [Required] [Display(Name = "MessageTo", Description = "The email message will be sent to.")] + [DataType(DataType.Text)] + [Formattable] public string MessageTo { get; set; } [Required] [Display(Name = "MessageSubject", Description = "The subject line for this email message.")] + [DataType(DataType.Text)] + [Formattable] public string MessageSubject { get; set; } [Required] [Display(Name = "MessageBody", Description = "The message body.")] + [DataType(DataType.MultilineText)] + [Formattable] public string MessageBody { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs index d1840782d..e4e056ba7 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs @@ -22,10 +22,12 @@ namespace Squidex.Extensions.Actions.Fastly { [Required] [Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")] + [DataType(DataType.Text)] public string ApiKey { get; set; } [Required] [Display(Name = "Service Id", Description = "The ID of the fastly service.")] + [DataType(DataType.Text)] public string ServiceId { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 6bbf1bb30..65eddcefd 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs @@ -22,26 +22,36 @@ namespace Squidex.Extensions.Actions.Medium { [Required] [Display(Name = "Access Token", Description = "The self issued access token.")] + [DataType(DataType.Text)] public string AccessToken { get; set; } [Required] [Display(Name = "Title", Description = "The title, used for the url.")] + [DataType(DataType.Text)] + [Formattable] public string Title { get; set; } [Required] [Display(Name = "Content", Description = "The content, either html or markdown.")] + [DataType(DataType.MultilineText)] + [Formattable] public string Content { get; set; } [Display(Name = "Canonical Url", Description = "The original home of this content, if it was originally published elsewhere.")] + [DataType(DataType.Text)] + [Formattable] public string CanonicalUrl { get; set; } [Display(Name = "PublicationId", Description = "Optional publication id.")] + [DataType(DataType.Text)] public string PublicationId { get; set; } [Display(Name = "Tags", Description = "The optional comma separated list of tags.")] + [DataType(DataType.Text)] public string Tags { get; set; } [Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")] + [DataType(DataType.Custom)] public bool IsHtml { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index 4672a8d61..0978a79a4 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs @@ -22,10 +22,13 @@ namespace Squidex.Extensions.Actions.Prerender { [Required] [Display(Name = "Token", Description = "The prerender token from your account.")] + [DataType(DataType.Text)] + [Formattable] public string Token { get; set; } [Required] [Display(Name = "Url", Description = "The url to recache.")] + [DataType(DataType.Text)] public string Url { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index 4e2863f8a..e85c19f32 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -25,10 +25,14 @@ namespace Squidex.Extensions.Actions.Slack [AbsoluteUrl] [Required] [Display(Name = "Webhook Url", Description = "The slack webhook url.")] + [DataType(DataType.Text)] + [Formattable] public Uri WebhookUrl { get; set; } [Required] [Display(Name = "Text", Description = "The text that is sent as message to slack.")] + [DataType(DataType.MultilineText)] + [Formattable] public string Text { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index cc0fa3d17..c23b3dd98 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -22,14 +22,17 @@ namespace Squidex.Extensions.Actions.Twitter { [Required] [Display(Name = "Access Token", Description = " The generated access token.")] + [DataType(DataType.Text)] public string AccessToken { get; set; } [Required] [Display(Name = "Access Secret", Description = " The generated access secret.")] + [DataType(DataType.Text)] public string AccessSecret { get; set; } [Required] [Display(Name = "Text", Description = "The text that is sent as tweet to twitter.")] + [DataType(DataType.MultilineText)] public string Text { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index 0877c37c9..cdd50c429 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -24,10 +24,13 @@ namespace Squidex.Extensions.Actions.Webhook { [AbsoluteUrl] [Required] - [Display(Name = "Url", Description = "he url to the webhook.")] + [Display(Name = "Url", Description = "The url to the webhook.")] + [DataType(DataType.Text)] + [Formattable] public Uri Url { get; set; } [Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")] + [DataType(DataType.Text)] public string SharedSecret { get; set; } } } diff --git a/extensions/Squidex.Extensions/SquidexExtensions.cs b/extensions/Squidex.Extensions/SquidexExtensions.cs new file mode 100644 index 000000000..9803ae4ce --- /dev/null +++ b/extensions/Squidex.Extensions/SquidexExtensions.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; + +namespace Squidex.Extensions +{ + public static class SquidexExtensions + { + public static readonly Assembly Assembly = typeof(SquidexExtensions).Assembly; + } +} diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 19c375aa0..315f01547 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -6,17 +6,19 @@ // ========================================================================== using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Config.Domain; using Squidex.Infrastructure.DependencyInjection; using Squidex.Pipeline; +using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; namespace Squidex.Config.Web { public static class WebServices { - public static void AddMyMvc(this IServiceCollection services) + public static void AddMyMvc(this IServiceCollection services, IConfiguration config) { services.AddSingletonAs() .AsSelf(); @@ -47,7 +49,9 @@ namespace Squidex.Config.Web options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); - }).AddMySerializers(); + }) + .AddMySerializers() + .AddMyPlugins(config); services.AddCors(); services.AddRouting(); diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs index 94de2338e..f2397b7ed 100644 --- a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -8,9 +8,11 @@ using System; using System.Reflection; using McMaster.NETCore.Plugins; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Squidex.Extensions; using Squidex.Infrastructure.Plugins; namespace Squidex.Pipeline.Plugins @@ -19,14 +21,14 @@ namespace Squidex.Pipeline.Plugins { private static readonly Type[] SharedTypes = { typeof(IPlugin) }; - public static void AddPlugins(IMvcBuilder mvcBuilder, IConfiguration configuration) + public static void AddMyPlugins(this IMvcBuilder mvcBuilder, IConfiguration configuration) { + var pluginManager = new PluginManager(); + var options = configuration.Get(); if (options.Plugins != null) { - var pluginManager = new PluginManager(); - foreach (var pluginPath in options.Plugins) { PluginLoader plugin = null; @@ -58,9 +60,19 @@ namespace Squidex.Pipeline.Plugins pluginManager.Add(pluginAssembly); } } - - mvcBuilder.Services.AddSingleton(pluginManager); } + + pluginManager.Add(SquidexExtensions.Assembly); + pluginManager.ConfigureServices(mvcBuilder.Services, configuration); + + mvcBuilder.Services.AddSingleton(pluginManager); + } + + public static void UsePlugins(this IApplicationBuilder app) + { + var pluginManager = app.ApplicationServices.GetRequiredService(); + + pluginManager.Configure(app); } private static void AddParts(IMvcBuilder mvcBuilder, Assembly assembly) diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 61dd0a38d..486c5ea25 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -31,6 +31,7 @@ using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Translations; using Squidex.Pipeline; +using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; namespace Squidex @@ -62,7 +63,7 @@ namespace Squidex services.AddMyInfrastructureServices(config); services.AddMyLoggingServices(config); services.AddMyMigrationServices(); - services.AddMyMvc(); + services.AddMyMvc(config); services.AddMyRuleServices(); services.AddMySerializers(); services.AddMyStoreServices(config); @@ -124,6 +125,8 @@ namespace Squidex app.ConfigureOrleansDashboard(); app.ConfigureIdentityServer(); app.ConfigureFrontend(); + + app.UsePlugins(); } } } diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html index 6ad970abc..977302dab 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html @@ -1,7 +1,11 @@
- + + + + +
@@ -14,7 +18,11 @@
-
+ + + + +
diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss index fca4de50b..b22df1cd7 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss @@ -27,6 +27,10 @@ height: 3rem; } + .icon { + font-size: 20px; + } + .svg-icon { width: 20px; } @@ -56,6 +60,7 @@ } &-icon { + color: $color-dark-foreground; display: inline-block; margin-right: .5rem; position: relative; @@ -63,6 +68,10 @@ line-height: 1px; } + .icon { + font-size: 30px; + } + .svg-icon { width: 30px; } diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts b/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts index 849f26c94..3c21249db 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.ts @@ -9,6 +9,7 @@ import { Component, OnInit } from '@angular/core'; import { onErrorResumeNext } from 'rxjs/operators'; import { + ALL_TRIGGERS, AppsState, DialogModel, RuleDto, @@ -30,7 +31,7 @@ export class RulesPageComponent implements OnInit { public wizardRule: RuleDto | null; public ruleActions: { [name: string]: RuleElementDto }; - public ruleTriggers: { [name: string]: RuleElementDto }; + public ruleTriggers = ALL_TRIGGERS; constructor( public readonly appsState: AppsState, @@ -48,11 +49,6 @@ export class RulesPageComponent implements OnInit { this.ruleActions = actions; }); - this.rulesService.getTriggers() - .subscribe(triggers => { - this.ruleTriggers = triggers; - }); - this.schemasState.load().pipe(onErrorResumeNext()).subscribe(); } diff --git a/src/Squidex/app/shared/services/rules.service.spec.ts b/src/Squidex/app/shared/services/rules.service.spec.ts index f765ab29d..c5d6fe99b 100644 --- a/src/Squidex/app/shared/services/rules.service.spec.ts +++ b/src/Squidex/app/shared/services/rules.service.spec.ts @@ -77,45 +77,8 @@ describe('RulesService', () => { }); expect(actions!).toEqual({ - 'action1': new RuleElementDto('display1', 'description1', '#111', '', 'link1'), - 'action2': new RuleElementDto('display2', 'description2', '#222', '', 'link2') - }); - })); - - it('should make get request to get triggers', - inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => { - - let triggers: { [ name: string ]: RuleElementDto }; - - rulesService.getTriggers().subscribe(result => { - triggers = result; - }); - - const req = httpMock.expectOne('http://service/p/api/rules/triggers'); - - expect(req.request.method).toEqual('GET'); - expect(req.request.headers.get('If-Match')).toBeNull(); - - req.flush({ - 'trigger2': { - display: 'display2', - description: 'description2', - iconColor: '#222', - iconImage: '', - readMore: 'link2' - }, - 'trigger1': { - display: 'display1', - description: 'description1', - iconColor: '#111', - iconImage: '', - readMore: 'link1' - } - }); - - expect(triggers!).toEqual({ - 'trigger1': new RuleElementDto('display1', 'description1', '#111', '', 'link1'), - 'trigger2': new RuleElementDto('display2', 'description2', '#222', '', 'link2') + 'action1': new RuleElementDto('display1', 'description1', '#111', '', null, 'link1'), + 'action2': new RuleElementDto('display2', 'description2', '#222', '', null, 'link2') }); })); diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 6f395ef87..cb2eae816 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -21,12 +21,39 @@ import { Versioned } from '@app/framework'; +export const ALL_TRIGGERS = { + 'ContentChanged': { + description: 'For content changes like created, updated, published, unpublished...', + display: 'Content changed', + iconColor: '#3389ff', + iconCode: 'contents' + }, + 'AssetChanged': { + description: 'For asset changes like uploaded, updated (reuploaded), renamed, deleted...', + display: 'Asset changed', + iconColor: '#3389ff', + iconCode: 'assets' + }, + 'SchemaChanged': { + description: 'When a schema definition has been created, updated, published or deleted...', + display: 'Schema changed', + iconColor: '#3389ff', + iconCode: 'schemas'}, + 'Usage': { + description: 'When monthly API calls exceed a specified limit for one time a month...', + display: 'Usage exceeded', + iconColor: '#3389ff', + iconCode: 'dashboard' + } +}; + export class RuleElementDto { constructor( public readonly display: string, public readonly description: string, public readonly iconColor: string, public readonly iconImage: string, + public readonly iconCode: string | null, public readonly readMore: string ) { } @@ -120,27 +147,7 @@ export class RulesService { for (let key of Object.keys(items).sort()) { const value = items[key]; - result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, value.readMore); - } - - return result; - }), - pretifyError('Failed to load Rules. Please reload.')); - } - - public getTriggers(): Observable<{ [name: string]: RuleElementDto }> { - const url = this.apiUrl.buildUrl('api/rules/triggers'); - - return HTTP.getVersioned(this.http, url).pipe( - map(response => { - const items: { [name: string]: any } = response.payload.body; - - const result: { [name: string]: RuleElementDto } = {}; - - for (let key of Object.keys(items).sort()) { - const value = items[key]; - - result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, value.readMore); + result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, null, value.readMore); } return result; From 82213d5871feeca8c66fc2e6fee76e0022bb6811 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Feb 2019 18:53:04 +0100 Subject: [PATCH 03/55] Plugin system for rule actions. --- .../ElasticSearch/ElasticSearchAction.cs | 4 +- .../Actions/Slack/SlackAction.cs | 1 - .../HandleRules/RuleActionDefinition.cs | 3 + .../HandleRules/RuleActionProperty.cs | 24 +++ .../HandleRules/RuleActionPropertyEditor.cs | 20 +++ .../HandleRules/RuleActionRegistry.cs | 77 +++++++- src/Squidex.Infrastructure/Plugins/IPlugin.cs | 1 - .../Rules/Models/RuleActionConverter.cs | 1 - .../Rules/Models/RuleActionProcessor.cs | 1 - .../Rules/Models/RuleElementDto.cs | 18 ++ .../Rules/Models/RuleElementPropertyDto.cs | 48 +++++ .../Api/Controllers/Rules/RulesController.cs | 4 +- src/Squidex/Config/Domain/RuleServices.cs | 1 - .../app/features/rules/declarations.ts | 12 +- src/Squidex/app/features/rules/module.ts | 28 +-- .../actions/algolia-action.component.html | 43 ----- .../actions/algolia-action.component.scss | 2 - .../rules/actions/algolia-action.component.ts | 42 ----- .../actions/azure-queue-action.component.html | 29 --- .../actions/azure-queue-action.component.scss | 2 - .../actions/azure-queue-action.component.ts | 40 ----- .../actions/discourse-action.component.html | 99 ----------- .../actions/discourse-action.component.scss | 6 - .../actions/discourse-action.component.ts | 56 ------ .../elastic-search-action.component.html | 71 -------- .../elastic-search-action.component.scss | 2 - .../elastic-search-action.component.ts | 46 ----- .../rules/actions/email-action.component.html | 128 -------------- .../rules/actions/email-action.component.scss | 6 - .../rules/actions/email-action.component.ts | 70 -------- .../actions/fastly-action.component.html | 29 --- .../actions/fastly-action.component.scss | 2 - .../rules/actions/fastly-action.component.ts | 37 ---- .../actions/generic-action.component.html | 34 ++++ .../actions/generic-action.component.scss | 6 + .../rules/actions/generic-action.component.ts | 44 +++++ .../actions/medium-action.component.html | 96 ---------- .../actions/medium-action.component.scss | 6 - .../rules/actions/medium-action.component.ts | 54 ------ .../actions/prerender-action.component.html | 29 --- .../actions/prerender-action.component.scss | 2 - .../actions/prerender-action.component.ts | 37 ---- .../rules/actions/slack-action.component.html | 29 --- .../rules/actions/slack-action.component.scss | 6 - .../rules/actions/slack-action.component.ts | 37 ---- .../rules/actions/tweet-action.component.html | 55 ------ .../rules/actions/tweet-action.component.scss | 6 - .../rules/actions/tweet-action.component.ts | 98 ----------- .../actions/webhook-action.component.html | 29 --- .../actions/webhook-action.component.scss | 2 - .../rules/actions/webhook-action.component.ts | 35 ---- .../pages/rules/rule-element.component.html | 10 +- .../pages/rules/rule-element.component.scss | 9 +- .../pages/rules/rule-wizard.component.html | 85 +-------- .../app/shared/services/rules.service.spec.ts | 32 +++- .../app/shared/services/rules.service.ts | 33 +++- .../HandleRules/RuleElementRegistry.cs | 166 ++++++++++++++++++ 57 files changed, 519 insertions(+), 1374 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs create mode 100644 src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss create mode 100644 src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.scss delete mode 100644 src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 44f0ce8c2..00b4a74db 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -42,12 +42,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch [Display(Name = "Username", Description = "The optional username.")] [DataType(DataType.Text)] - [Formattable] public string Username { get; set; } [Display(Name = "Password", Description = "The optional password.")] - [DataType(DataType.Password)] - [Formattable] + [DataType(DataType.Text)] public string Password { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index e85c19f32..47e1dddc7 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -26,7 +26,6 @@ namespace Squidex.Extensions.Actions.Slack [Required] [Display(Name = "Webhook Url", Description = "The slack webhook url.")] [DataType(DataType.Text)] - [Formattable] public Uri WebhookUrl { get; set; } [Required] diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs index 595f6d56b..97a9110d1 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionDefinition.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; namespace Squidex.Domain.Apps.Core.HandleRules { @@ -22,5 +23,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules public string Display { get; set; } public string Description { get; set; } + + public List Properties { get; } = new List(); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs new file mode 100644 index 000000000..1611e76ea --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionProperty.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.HandleRules +{ + public sealed class RuleActionProperty + { + public RuleActionPropertyEditor Editor { get; set; } + + public string Name { get; set; } + + public string Display { get; set; } + + public string Description { get; set; } + + public bool IsFormattable { get; set; } + + public bool IsRequired { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs new file mode 100644 index 000000000..469e01a35 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionPropertyEditor.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.HandleRules +{ + public enum RuleActionPropertyEditor + { + Checkbox, + Email, + Number, + Password, + Text, + TextArea, + Url + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs index a17a58872..c1eebfb1e 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using Squidex.Domain.Apps.Core.Rules; @@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules public static class RuleActionRegistry { private const string ActionSuffix = "Action"; - private const string ActionSuffixV2 = "Action"; + private const string ActionSuffixV2 = "ActionV2"; private static readonly HashSet ActionHandlerTypes = new HashSet(); private static readonly Dictionary ActionTypes = new Dictionary(); @@ -70,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules var name = GetActionName(actionType); - ActionTypes[name] = + var definition = new RuleActionDefinition { Type = actionType, @@ -81,9 +82,81 @@ namespace Squidex.Domain.Apps.Core.HandleRules ReadMore = metadata.ReadMore }; + foreach (var property in actionType.GetProperties()) + { + if (property.CanRead && property.CanWrite) + { + var actionProperty = new RuleActionProperty { Name = property.Name.ToCamelCase(), Display = property.Name }; + + var display = property.GetCustomAttribute(); + + if (!string.IsNullOrWhiteSpace(display?.Name)) + { + actionProperty.Display = display.Name; + } + + if (!string.IsNullOrWhiteSpace(display?.Description)) + { + actionProperty.Description = display.Description; + } + + var type = property.PropertyType; + + if ((property.GetCustomAttribute() != null || (type.IsValueType && !IsNullable(type))) && type != typeof(bool) && type != typeof(bool?)) + { + actionProperty.IsRequired = true; + } + + if (property.GetCustomAttribute() != null) + { + actionProperty.IsFormattable = true; + } + + var dataType = property.GetCustomAttribute()?.DataType; + + if (type == typeof(bool) || type == typeof(bool?)) + { + actionProperty.Editor = RuleActionPropertyEditor.Checkbox; + } + else if (type == typeof(int) || type == typeof(int?)) + { + actionProperty.Editor = RuleActionPropertyEditor.Number; + } + else if (dataType == DataType.Url) + { + actionProperty.Editor = RuleActionPropertyEditor.Url; + } + else if (dataType == DataType.Password) + { + actionProperty.Editor = RuleActionPropertyEditor.Password; + } + else if (dataType == DataType.EmailAddress) + { + actionProperty.Editor = RuleActionPropertyEditor.Email; + } + else if (dataType == DataType.MultilineText) + { + actionProperty.Editor = RuleActionPropertyEditor.TextArea; + } + else + { + actionProperty.Editor = RuleActionPropertyEditor.Text; + } + + definition.Properties.Add(actionProperty); + } + } + + ActionTypes[name] = definition; + ActionHandlerTypes.Add(actionType.GetCustomAttribute().HandlerType); } + private static bool IsNullable(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + public static TypeNameRegistry MapRuleActions(this TypeNameRegistry typeNameRegistry) { foreach (var actionType in ActionTypes.Values) diff --git a/src/Squidex.Infrastructure/Plugins/IPlugin.cs b/src/Squidex.Infrastructure/Plugins/IPlugin.cs index 72ed11047..2c8fc4674 100644 --- a/src/Squidex.Infrastructure/Plugins/IPlugin.cs +++ b/src/Squidex.Infrastructure/Plugins/IPlugin.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs index fe5666dad..b36243b4f 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Extensions.Actions; namespace Squidex.Areas.Api.Controllers.Rules.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs index 2ea9995b8..b2abd5b91 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs @@ -13,7 +13,6 @@ using NSwag.SwaggerGeneration.Processors; using NSwag.SwaggerGeneration.Processors.Contexts; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Extensions.Actions; namespace Squidex.Areas.Api.Controllers.Rules.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs index e419be526..910e87c38 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementDto.cs @@ -6,6 +6,9 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using System.Linq; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Rules.Models { @@ -37,5 +40,20 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models /// The optional link to the product that is integrated. /// public string ReadMore { get; set; } + + /// + /// The properties. + /// + [Required] + public RuleElementPropertyDto[] Properties { get; set; } + + public static RuleElementDto FromDefinition(RuleActionDefinition definition) + { + var result = SimpleMapper.Map(definition, new RuleElementDto()); + + result.Properties = definition.Properties.Select(x => SimpleMapper.Map(x, new RuleElementPropertyDto())).ToArray(); + + return result; + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs new file mode 100644 index 000000000..81170e36f --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleElementPropertyDto.cs @@ -0,0 +1,48 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using Squidex.Domain.Apps.Core.HandleRules; + +namespace Squidex.Areas.Api.Controllers.Rules.Models +{ + public sealed class RuleElementPropertyDto + { + /// + /// The html editor. + /// + [Required] + public RuleActionPropertyEditor Editor { get; set; } + + /// + /// The name of the editor. + /// + [Required] + public string Name { get; set; } + + /// + /// The label to use. + /// + [Required] + public string Display { get; set; } + + /// + /// The optional description. + /// + public string Description { get; set; } + + /// + /// Indicates if the property is formattable. + /// + public bool IsFormattable { get; set; } + + /// + /// Indicates if the property is required. + /// + public bool IsRequired { get; set; } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 752b4e041..d3d7edd1b 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -17,10 +17,8 @@ using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Repositories; -using Squidex.Extensions.Actions; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; using Squidex.Shared; @@ -58,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(0)] public IActionResult GetActions() { - var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => SimpleMapper.Map(x.Value, new RuleElementDto())); + var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); Response.Headers[HeaderNames.ETag] = RuleActionsEtag; diff --git a/src/Squidex/Config/Domain/RuleServices.cs b/src/Squidex/Config/Domain/RuleServices.cs index 5bdc3f45d..685bc2077 100644 --- a/src/Squidex/Config/Domain/RuleServices.cs +++ b/src/Squidex/Config/Domain/RuleServices.cs @@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Extensions.Actions; using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; diff --git a/src/Squidex/app/features/rules/declarations.ts b/src/Squidex/app/features/rules/declarations.ts index 6b65eccfb..e8d6e49d0 100644 --- a/src/Squidex/app/features/rules/declarations.ts +++ b/src/Squidex/app/features/rules/declarations.ts @@ -5,17 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -export * from './pages/rules/actions/algolia-action.component'; -export * from './pages/rules/actions/azure-queue-action.component'; -export * from './pages/rules/actions/discourse-action.component'; -export * from './pages/rules/actions/elastic-search-action.component'; -export * from './pages/rules/actions/email-action.component'; -export * from './pages/rules/actions/fastly-action.component'; -export * from './pages/rules/actions/medium-action.component'; -export * from './pages/rules/actions/prerender-action.component'; -export * from './pages/rules/actions/slack-action.component'; -export * from './pages/rules/actions/tweet-action.component'; -export * from './pages/rules/actions/webhook-action.component'; +export * from './pages/rules/actions/generic-action.component'; export * from './pages/rules/triggers/asset-changed-trigger.component'; export * from './pages/rules/triggers/content-changed-trigger.component'; diff --git a/src/Squidex/app/features/rules/module.ts b/src/Squidex/app/features/rules/module.ts index 95551399c..7ee3218b3 100644 --- a/src/Squidex/app/features/rules/module.ts +++ b/src/Squidex/app/features/rules/module.ts @@ -15,26 +15,16 @@ import { } from '@app/shared'; import { - AlgoliaActionComponent, AssetChangedTriggerComponent, - AzureQueueActionComponent, ContentChangedTriggerComponent, - DiscourseActionComponent, - ElasticSearchActionComponent, - EmailActionComponent, - FastlyActionComponent, - MediumActionComponent, - PrerenderActionComponent, + GenericActionComponent, RuleElementComponent, RuleEventBadgeClassPipe, RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, SchemaChangedTriggerComponent, - SlackActionComponent, - TweetActionComponent, - UsageTriggerComponent, - WebhookActionComponent + UsageTriggerComponent } from './declarations'; const routes: Routes = [ @@ -64,26 +54,16 @@ const routes: Routes = [ RouterModule.forChild(routes) ], declarations: [ - AlgoliaActionComponent, AssetChangedTriggerComponent, - AzureQueueActionComponent, ContentChangedTriggerComponent, - DiscourseActionComponent, - EmailActionComponent, - ElasticSearchActionComponent, - FastlyActionComponent, - MediumActionComponent, - PrerenderActionComponent, + GenericActionComponent, RuleElementComponent, RuleEventBadgeClassPipe, RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, SchemaChangedTriggerComponent, - SlackActionComponent, - TweetActionComponent, - UsageTriggerComponent, - WebhookActionComponent + UsageTriggerComponent ] }) export class SqxFeatureRulesModule { } \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html deleted file mode 100644 index 10e8f129e..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- - -
- - - - - - The ID to you algolia application. - -
-
- -
- - -
- - - - - - The API Key to access you algolia app. - -
-
- -
- - -
- - - - - - The name of the index. You can use advanced formatting (read help section). - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts deleted file mode 100644 index 0ac2c1641..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/algolia-action.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-algolia-action', - styleUrls: ['./algolia-action.component.scss'], - templateUrl: './algolia-action.component.html' -}) -export class AlgoliaActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('appId', - new FormControl(this.action.appId || '', [ - Validators.required - ])); - - this.actionForm.setControl('apiKey', - new FormControl(this.action.apiKey || '', [ - Validators.required - ])); - - this.actionForm.setControl('indexName', - new FormControl(this.action.indexName || '$SCHEMA_NAME', [ - Validators.required - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html deleted file mode 100644 index ffae8dd67..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- - - - - - The connection string to the storage account. - -
-
- -
- - -
- - - - - - The name of the queue. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts deleted file mode 100644 index 8df30f90e..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/azure-queue-action.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -import { ValidatorsEx } from '@app/shared'; - -@Component({ - selector: 'sqx-azure-queue-action', - styleUrls: ['./azure-queue-action.component.scss'], - templateUrl: './azure-queue-action.component.html' -}) -export class AzureQueueActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('connectionString', - new FormControl(this.action.connectionString || '', [ - Validators.required - ])); - - this.actionForm.setControl('queue', - new FormControl(this.action.queue || 'squidex', [ - Validators.required, - ValidatorsEx.pattern('[a-z][a-z0-9]{2,}(\-[a-z0-9]+)*', 'Name must be a valid azure queue name.') - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html deleted file mode 100644 index e22fd9080..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.html +++ /dev/null @@ -1,99 +0,0 @@ -
-
- - -
- - - - - - The url to your discourse server. - -
-
- -
- - -
- - - - - - The api key to authenticate to your discourse server. - -
-
- -
- - -
- - - - - - The api username to authenticate to your discourse server. - -
-
- -
- - -
- - - - - - The text for your topic or post. Read the help section for information about advanced formatting. - -
-
- -
- - -
- - - - - - The optional title, when you want to create a topic. Read the help section for information about advanced formatting. - -
-
- -
- - -
- - - - - - The topic id when you want to create a post. - -
-
- -
- - -
- - - - - - The category id when you create a topic. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss deleted file mode 100644 index 756609665..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -textarea { - height: 150px; -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts deleted file mode 100644 index a2e25ac55..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/discourse-action.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-discourse-action', - styleUrls: ['./discourse-action.component.scss'], - templateUrl: './discourse-action.component.html' -}) -export class DiscourseActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('url', - new FormControl(this.action.url || '', [ - Validators.required - ])); - - this.actionForm.setControl('apiKey', - new FormControl(this.action.apiKey || '', [ - Validators.required - ])); - - this.actionForm.setControl('apiUsername', - new FormControl(this.action.apiUsername || '', [ - Validators.required - ])); - - this.actionForm.setControl('text', - new FormControl(this.action.text || '', [ - Validators.required - ])); - - this.actionForm.setControl('title', - new FormControl(this.action.title)); - - this.actionForm.setControl('topic', - new FormControl(this.action.topic)); - - this.actionForm.setControl('category', - new FormControl(this.action.category)); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html deleted file mode 100644 index fee16364c..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
- - -
- - - - - - The url to your elastic search instance. - -
-
- -
- - -
- - - - - - The username for authentication. Highly recommended. - -
-
- -
- - -
- - - - - - The password for authentication. Highly recommended. - -
-
- -
- - -
- - - - - - The name of the index. You can use advanced formatting (read help section). - -
-
- -
- - -
- - - - - - The name of the type. You can use advanced formatting (read help section). - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts deleted file mode 100644 index 8e6e0c8f2..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/elastic-search-action.component.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-elastic-search-action', - styleUrls: ['./elastic-search-action.component.scss'], - templateUrl: './elastic-search-action.component.html' -}) -export class ElasticSearchActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('host', - new FormControl(this.action.host || '', [ - Validators.required - ])); - - this.actionForm.setControl('indexName', - new FormControl(this.action.indexName || '$APP_NAME', [ - Validators.required - ])); - - this.actionForm.setControl('indexType', - new FormControl(this.action.indexType || '$SCHEMA_NAME')); - - this.actionForm.setControl('username', - new FormControl(this.action.username)); - - this.actionForm.setControl('password', - new FormControl(this.action.password)); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html deleted file mode 100644 index c083bb1cc..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.html +++ /dev/null @@ -1,128 +0,0 @@ -
-
- - -
- - - - - - The IP address or host to the SMTP server. - -
-
- -
- - -
- - - - - - The port to the SMTP server. - -
-
- -
-
-
- - -
- - Specify whether the SMTP client uses Secure Sockets Layer (SSL) to encrypt the connection. - -
-
- -
- - -
- - - - - - The username for the SMTP server. - -
-
- -
- - -
- - - - - - The password for the SMTP server. - -
-
- -
- - -
- - - - - - The email sending address. Read the help section for information about advanced formatting. - -
-
- -
- - -
- - - - - - The email message will be sent to. Read the help section for information about advanced formatting. - -
-
- -
- - -
- - - - - - The subject line for this email message. Read the help section for information about advanced formatting. - -
-
- - -
- - -
- - - - - - The message body. Read the help section for information about advanced formatting. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss deleted file mode 100644 index 51468cba5..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -textarea { - height: 250px; -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts deleted file mode 100644 index 31761bef8..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/email-action.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-email-action', - styleUrls: ['./email-action.component.scss'], - templateUrl: './email-action.component.html' -}) -export class EmailActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('serverHost', - new FormControl(this.action.serverHost || 'smtp.gmail.com', [ - Validators.required - ])); - - this.actionForm.setControl('serverPort', - new FormControl(this.action.serverPort || 465, [ - Validators.required - ])); - - this.actionForm.setControl('serverUseSsl', - new FormControl(this.action.serverUseSsl || true)); - - this.actionForm.setControl('serverUsername', - new FormControl(this.action.serverUsername || '', [ - Validators.required - ])); - - this.actionForm.setControl('serverPassword', - new FormControl(this.action.serverPassword || '', [ - Validators.required - ])); - - this.actionForm.setControl('messageFrom', - new FormControl(this.action.messageFrom || '', [ - Validators.required - ])); - - this.actionForm.setControl('messageTo', - new FormControl(this.action.messageTo || '', [ - Validators.required - ])); - - this.actionForm.setControl('messageSubject', - new FormControl(this.action.messageSubject || '', [ - Validators.required - ])); - - this.actionForm.setControl('messageBody', - new FormControl(this.action.messageBody || '', [ - Validators.required - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html deleted file mode 100644 index b089b622c..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- - - - - - The service ID of the fastly account. - -
-
- -
- - -
- - - - - - The API key for the fastly account. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts deleted file mode 100644 index 192216769..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/fastly-action.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-fastly-action', - styleUrls: ['./fastly-action.component.scss'], - templateUrl: './fastly-action.component.html' -}) -export class FastlyActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('serviceId', - new FormControl(this.action.serviceId || '', [ - Validators.required - ])); - - this.actionForm.setControl('apiKey', - new FormControl(this.action.apiKey || '', [ - Validators.required - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html new file mode 100644 index 000000000..5c0438347 --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html @@ -0,0 +1,34 @@ +
+
+ + +
+ + +
+
+ +
+
+
+ + +
+
+
+ +
+
+ + + {{property.description}} + + + You can use advanced formatting: Documentation + + +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss new file mode 100644 index 000000000..6f7a94e21 --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss @@ -0,0 +1,6 @@ +@import '_vars'; +@import '_mixins'; + +.form-check { + padding-top: .5rem; +} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts new file mode 100644 index 000000000..06992b17e --- /dev/null +++ b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.ts @@ -0,0 +1,44 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +import { RuleElementDto } from '@app/shared'; + +@Component({ + selector: 'sqx-generic-action', + styleUrls: ['./generic-action.component.scss'], + templateUrl: './generic-action.component.html' +}) +export class GenericActionComponent implements OnInit { + @Input() + public definition: RuleElementDto; + + @Input() + public action: any; + + @Input() + public actionForm: FormGroup; + + @Input() + public actionFormSubmitted = false; + + public ngOnInit() { + for (let property of this.definition.properties) { + const validators = []; + + if (property.isRequired) { + validators.push(Validators.required); + } + + const control = new FormControl(this.action[property.name] || '', validators); + + this.actionForm.setControl(property.name, control); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html deleted file mode 100644 index d92a5c7e8..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.html +++ /dev/null @@ -1,96 +0,0 @@ -
-
- - -
- - - - - - The self issued access token. Can be created under https://medium.com/me/settings. - -
-
- -
- - -
- - - - - - The title of the post. Note that this title is used for SEO and when rendering the post as a listing. - -
-
- -
- - -
- - - - - - The body of the post, in a valid, semantic, HTML fragment, or Markdown. - -
-
- -
-
-
- - -
-
-
- -
- - -
- - - - - - The original home of this content, if it was originally published elsewhere. - -
-
- -
- - -
- - - - - - Optional comma-separated list of tags. - -
-
- -
- - -
- - - - - - Optional publication id. Go to https://medium.com/[PUBLICATION]?format=json to fetch the id. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss deleted file mode 100644 index 756609665..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -textarea { - height: 150px; -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts deleted file mode 100644 index a14f0841c..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/medium-action.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-medium-action', - styleUrls: ['./medium-action.component.scss'], - templateUrl: './medium-action.component.html' -}) -export class MediumActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('accessToken', - new FormControl(this.action.accessToken || '', [ - Validators.required - ])); - - this.actionForm.setControl('title', - new FormControl(this.action.title || '', [ - Validators.required - ])); - - this.actionForm.setControl('content', - new FormControl(this.action.content || '', [ - Validators.required - ])); - - this.actionForm.setControl('canonicalUrl', - new FormControl(this.action.canonicalUrl || '')); - - this.actionForm.setControl('tags', - new FormControl(this.action.tags || '')); - - this.actionForm.setControl('publicationId', - new FormControl(this.action.publicationId || '')); - - this.actionForm.setControl('isHtml', - new FormControl(this.action.isHtml || false)); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.html deleted file mode 100644 index 63b5a2319..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- - - - - - The prerender token from your account. - -
-
- -
- - -
- - - - - - The url to recache. Read the help section for information about advanced formatting. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts deleted file mode 100644 index adf25fb6d..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/prerender-action.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-prerender-action', - styleUrls: ['./prerender-action.component.scss'], - templateUrl: './prerender-action.component.html' -}) -export class PrerenderActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('token', - new FormControl(this.action.token || '', [ - Validators.required - ])); - - this.actionForm.setControl('url', - new FormControl(this.action.url || '', [ - Validators.required - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html deleted file mode 100644 index 0f21696a0..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- - - - - - The url to the incoming slack webhook. - -
-
- -
- - -
- - - - - - The text to send to slack. Read the help section for information about advanced formatting. - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss deleted file mode 100644 index 756609665..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -textarea { - height: 150px; -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts deleted file mode 100644 index 9a425591b..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/slack-action.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-slack-action', - styleUrls: ['./slack-action.component.scss'], - templateUrl: './slack-action.component.html' -}) -export class SlackActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('webhookUrl', - new FormControl(this.action.webhookUrl || '', [ - Validators.required - ])); - - this.actionForm.setControl('text', - new FormControl(this.action.text || '', [ - Validators.required - ])); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html deleted file mode 100644 index 341d20043..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.html +++ /dev/null @@ -1,55 +0,0 @@ -
-
-
- - - - - - - - - - - -
-
- -
- - -
- - - -
-
- -
- - -
- - - -
-
- -
- - -
- - - - - - The text to tweet. Read the help section for information about advanced formatting. - -
-
- \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss deleted file mode 100644 index 756609665..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -textarea { - height: 150px; -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts deleted file mode 100644 index 84b8b802c..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/tweet-action.component.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { HttpClient } from '@angular/common/http'; -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -import { DialogService } from '@app/shared'; - -@Component({ - selector: 'sqx-tweet-action', - styleUrls: ['./tweet-action.component.scss'], - templateUrl: './tweet-action.component.html' -}) -export class TweetActionComponent implements OnInit { - private request: any; - - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public isAuthenticating = false; - public isRedirected = false; - - public pinCode: string; - - constructor( - private readonly dialogs: DialogService, - private readonly httpClient: HttpClient - ) { - } - - public ngOnInit() { - this.actionForm.setControl('accessToken', - new FormControl(this.action.accessToken || '', [ - Validators.required - ])); - - this.actionForm.setControl('accessSecret', - new FormControl(this.action.accessSecret || '', [ - Validators.required - ])); - - this.actionForm.setControl('text', - new FormControl(this.action.text || '', [ - Validators.required, - Validators.maxLength(280) - ])); - } - - public auth() { - this.isAuthenticating = true; - - this.httpClient.get('api/rules/twitter/auth') - .subscribe((response: any) => { - this.request = { - requestToken: response.requestToken, - requestTokenSecret: response.requestTokenSecret - }; - - this.isAuthenticating = false; - this.isRedirected = true; - - window.open(response.authorizeUri, '_blank'); - }, () => { - this.dialogs.notifyError('Failed to authenticate with twitter.'); - - this.isAuthenticating = false; - this.isRedirected = false; - }); - } - - public complete() { - this.request.pinCode = this.pinCode; - - this.httpClient.post('api/rules/twitter/token', this.request) - .subscribe((response: any) => { - this.actionForm.get('accessToken')!.setValue(response.accessToken); - this.actionForm.get('accessSecret')!.setValue(response.accessTokenSecret); - - this.isRedirected = false; - }, () => { - this.dialogs.notifyError('Failed to request access token.'); - - this.isAuthenticating = false; - this.isRedirected = false; - }); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html deleted file mode 100644 index b8d4bb1d8..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- - -
- - - - - - The url where the events will be sent to. - -
-
- -
- - -
- - - - - - The shared secret will be used to add a header X-Signature=Base64(Sha256(RequestBody + Secret)) - -
-
-
\ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.scss deleted file mode 100644 index fbb752506..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '_vars'; -@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts b/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts deleted file mode 100644 index 767ed1442..000000000 --- a/src/Squidex/app/features/rules/pages/rules/actions/webhook-action.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { Component, Input, OnInit } from '@angular/core'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; - -@Component({ - selector: 'sqx-webhook-action', - styleUrls: ['./webhook-action.component.scss'], - templateUrl: './webhook-action.component.html' -}) -export class WebhookActionComponent implements OnInit { - @Input() - public action: any; - - @Input() - public actionForm: FormGroup; - - @Input() - public actionFormSubmitted = false; - - public ngOnInit() { - this.actionForm.setControl('url', - new FormControl(this.action.url || '', [ - Validators.required - ])); - - this.actionForm.setControl('sharedSecret', - new FormControl(this.action.sharedSecret || '')); - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html index 977302dab..695bdea88 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html @@ -1,16 +1,14 @@ -
-
+
+
-
-
- {{element.display}} -
+
+ {{element.display}}
diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss index b22df1cd7..aab579f44 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.scss @@ -5,9 +5,12 @@ & { @include transition(background-color .4s ease); cursor: pointer; + height: 3rem; + position: relative; } &-text { + @include absolute(0, 0, 0, 3rem); @include truncate; color: $color-dark-foreground; line-height: 3rem; @@ -17,16 +20,14 @@ } &-icon { + @include absolute(0, auto, 0, 0); + color: $color-dark-foreground; line-height: 3.2rem; font-size: 1.2rem; font-weight: normal; padding: 0 .8rem; } - .col { - height: 3rem; - } - .icon { font-size: 20px; } diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html index 16f1fca8e..9c5b40f42 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html @@ -79,85 +79,12 @@

{{ruleActions[actionType].display}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
diff --git a/src/Squidex/app/shared/services/rules.service.spec.ts b/src/Squidex/app/shared/services/rules.service.spec.ts index c5d6fe99b..da576b6f0 100644 --- a/src/Squidex/app/shared/services/rules.service.spec.ts +++ b/src/Squidex/app/shared/services/rules.service.spec.ts @@ -22,6 +22,7 @@ import { UpdateRuleDto, Version } from './../'; +import { RuleElementPropertyDto } from './rules.service'; describe('RulesService', () => { const now = DateTime.now(); @@ -65,20 +66,43 @@ describe('RulesService', () => { description: 'description2', iconColor: '#222', iconImage: '', - readMore: 'link2' + readMore: 'link2', + properties: [{ + name: 'property1', + editor: 'Editor1', + display: 'Display1', + description: 'Description1', + isRequired: true, + isFormattable: false + }, { + name: 'property2', + editor: 'Editor2', + display: 'Display2', + description: 'Description2', + isRequired: false, + isFormattable: true + }] }, 'action1': { display: 'display1', description: 'description1', iconColor: '#111', iconImage: '', - readMore: 'link1' + readMore: 'link1', + properties: [] } }); + const action1 = new RuleElementDto('display1', 'description1', '#111', '', null, 'link1', []); + + const action2 = new RuleElementDto('display2', 'description2', '#222', '', null, 'link2', [ + new RuleElementPropertyDto('property1', 'Editor1', 'Display1', 'Description1', false, true), + new RuleElementPropertyDto('property2', 'Editor2', 'Display2', 'Description2', true, false) + ]); + expect(actions!).toEqual({ - 'action1': new RuleElementDto('display1', 'description1', '#111', '', null, 'link1'), - 'action2': new RuleElementDto('display2', 'description2', '#222', '', null, 'link2') + 'action1': action1, + 'action2': action2 }); })); diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index cb2eae816..9ca980f73 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -54,7 +54,20 @@ export class RuleElementDto { public readonly iconColor: string, public readonly iconImage: string, public readonly iconCode: string | null, - public readonly readMore: string + public readonly readMore: string, + public readonly properties: RuleElementPropertyDto[] + ) { + } +} + +export class RuleElementPropertyDto { + constructor( + public readonly name: string, + public readonly editor: string, + public readonly display: string, + public readonly description: string, + public readonly isFormattable: boolean, + public readonly isRequired: boolean ) { } } @@ -147,7 +160,23 @@ export class RulesService { for (let key of Object.keys(items).sort()) { const value = items[key]; - result[key] = new RuleElementDto(value.display, value.description, value.iconColor, value.iconImage, null, value.readMore); + const properties = value.properties.map((property: any) => + new RuleElementPropertyDto( + property.name, + property.editor, + property.display, + property.description, + property.isFormattable, + property.isRequired + )); + + result[key] = new RuleElementDto( + value.display, + value.description, + value.iconColor, + value.iconImage, null, + value.readMore, + properties); } return result; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs new file mode 100644 index 000000000..3cef0cc4d --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs @@ -0,0 +1,166 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using FluentAssertions; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.HandleRules +{ + public class RuleElementRegistry + { + private abstract class MyRuleActionHandler : RuleActionHandler + { + protected MyRuleActionHandler(RuleEventFormatter formatter) + : base(formatter) + { + } + } + + [RuleActionHandler(typeof(MyRuleActionHandler))] + [RuleAction( + IconImage = "", + IconColor = "#1e5470", + Display = "Action display", + Description = "Action description.", + ReadMore = "https://www.readmore.com/")] + public sealed class MyRuleAction : RuleAction + { + [Required] + [Display(Name = "Url Name", Description = "Url Description")] + [DataType(DataType.Url)] + [Formattable] + public Uri Url { get; set; } + + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [DataType(DataType.Text)] + public string Text { get; set; } + + [DataType(DataType.MultilineText)] + public string TextMultiline { get; set; } + + [DataType(DataType.Password)] + public string Password { get; set; } + + public bool Boolean { get; set; } + + public bool? BooleanOptional { get; set; } + + public int Number { get; set; } + + public int? NumberOptional { get; set; } + } + + [Fact] + public void Should_create_definition() + { + var expected = new RuleActionDefinition + { + Type = typeof(MyRuleAction), + IconImage = "", + IconColor = "#1e5470", + Display = "Action display", + Description = "Action description.", + ReadMore = "https://www.readmore.com/" + }; + + expected.Properties.Add(new RuleActionProperty + { + Name = "url", + Display = "Url Name", + Description = "Url Description", + Editor = RuleActionPropertyEditor.Url, + IsFormattable = true, + IsRequired = true + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "email", + Display = "Email", + Description = null, + Editor = RuleActionPropertyEditor.Email, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "text", + Display = "Text", + Description = null, + Editor = RuleActionPropertyEditor.Text, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "textMultiline", + Display = "TextMultiline", + Description = null, + Editor = RuleActionPropertyEditor.TextArea, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "password", + Display = "Password", + Description = null, + Editor = RuleActionPropertyEditor.Password, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "boolean", + Display = "Boolean", + Description = null, + Editor = RuleActionPropertyEditor.Checkbox, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "booleanOptional", + Display = "BooleanOptional", + Description = null, + Editor = RuleActionPropertyEditor.Checkbox, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "number", + Display = "Number", + Description = null, + Editor = RuleActionPropertyEditor.Number, + IsRequired = true + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "numberOptional", + Display = "NumberOptional", + Description = null, + Editor = RuleActionPropertyEditor.Number, + IsRequired = false + }); + + RuleActionRegistry.Add(); + + var currentDefinition = RuleActionRegistry.Actions.Values.First(); + + currentDefinition.Should().BeEquivalentTo(expected); + } + } +} From 4b80525d92df60f15d71155f17fb226220b86341 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Feb 2019 20:28:46 +0100 Subject: [PATCH 04/55] Optional services and improved plugin loading. --- .../Actions/Twitter/TwitterPlugin.cs | 3 + .../Squidex.Extensions/SquidexExtensions.cs | 16 ---- .../Log/SemanticLogExtensions.cs | 12 ++- .../Plugins/IWebPlugin.cs | 2 +- .../Plugins/PluginManager.cs | 69 ++++++++++++-- .../Controllers/Rules/TwitterController.cs | 69 -------------- .../Areas/Api/Controllers/UI/UIController.cs | 7 +- src/Squidex/Config/Domain/AssetServices.cs | 8 +- src/Squidex/Config/Domain/EntitiesServices.cs | 2 +- .../Config/Domain/EventStoreServices.cs | 4 +- .../Config/Domain/InfrastructureServices.cs | 4 +- src/Squidex/Config/Domain/LoggingServices.cs | 2 +- src/Squidex/Config/Domain/StoreServices.cs | 24 ++--- .../Config/Domain/SubscriptionServices.cs | 6 +- .../Pipeline/Plugins/PluginExtensions.cs | 90 ++++++++++++++----- src/Squidex/Squidex.csproj | 3 - src/Squidex/WebStartup.cs | 5 +- src/Squidex/appsettings.json | 24 +++-- 18 files changed, 182 insertions(+), 168 deletions(-) delete mode 100644 extensions/Squidex.Extensions/SquidexExtensions.cs delete mode 100644 src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs index a5b120f4a..911db17d3 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs @@ -16,6 +16,9 @@ namespace Squidex.Extensions.Actions.Twitter { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { + services.Configure( + configuration.GetSection("twitter")); + RuleActionRegistry.Add(); } } diff --git a/extensions/Squidex.Extensions/SquidexExtensions.cs b/extensions/Squidex.Extensions/SquidexExtensions.cs deleted file mode 100644 index 9803ae4ce..000000000 --- a/extensions/Squidex.Extensions/SquidexExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Reflection; - -namespace Squidex.Extensions -{ - public static class SquidexExtensions - { - public static readonly Assembly Assembly = typeof(SquidexExtensions).Assembly; - } -} diff --git a/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs b/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs index 7c232bdf6..1b58a9d02 100644 --- a/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs +++ b/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs @@ -126,8 +126,16 @@ namespace Squidex.Infrastructure.Log return writer.WriteObject(nameof(exception), exception, (ctx, w) => { w.WriteProperty("type", ctx.GetType().FullName); - w.WriteProperty("message", ctx.Message); - w.WriteProperty("stackTrace", ctx.StackTrace); + + if (ctx.Message != null) + { + w.WriteProperty("message", ctx.Message); + } + + if (ctx.StackTrace != null) + { + w.WriteProperty("stackTrace", ctx.StackTrace); + } }); } diff --git a/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs index 2baddf066..d8cab06d5 100644 --- a/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs +++ b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Builder; namespace Squidex.Infrastructure.Plugins { - public interface IWebPlugin + public interface IWebPlugin : I { void Configure(IApplicationBuilder app); } diff --git a/src/Squidex.Infrastructure/Plugins/PluginManager.cs b/src/Squidex.Infrastructure/Plugins/PluginManager.cs index 08e2381ce..aa9fa477d 100644 --- a/src/Squidex.Infrastructure/Plugins/PluginManager.cs +++ b/src/Squidex.Infrastructure/Plugins/PluginManager.cs @@ -12,35 +12,59 @@ using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Squidex.Infrastructure.Log; namespace Squidex.Infrastructure.Plugins { public sealed class PluginManager { - private readonly HashSet plugins = new HashSet(); + private readonly HashSet loadedPlugins = new HashSet(); + private readonly List<(string Plugin, string Action, Exception Exception)> exceptions = new List<(string, string, Exception)>(); - public void Add(Assembly assembly) + public IReadOnlyCollection Plugins + { + get { return loadedPlugins; } + } + + public void Add(string name, Assembly assembly) { Guard.NotNull(assembly, nameof(assembly)); var pluginTypes = assembly.GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract); + .Where(t => typeof(IPlugin).IsAssignableFrom(t)) + .Where(t => !t.IsAbstract); foreach (var pluginType in pluginTypes) { - var plugin = (IPlugin)Activator.CreateInstance(pluginType); + try + { + var plugin = (IPlugin)Activator.CreateInstance(pluginType); - plugins.Add(plugin); + loadedPlugins.Add(plugin); + } + catch (Exception ex) + { + LogException(name, "Instantiating", ex); + } } } + public void LogException(string plugin, string action, Exception exception) + { + Guard.NotNull(plugin, nameof(plugin)); + Guard.NotNull(action, nameof(action)); + Guard.NotNull(exception, nameof(exception)); + + exceptions.Add((plugin, action, exception)); + } + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { Guard.NotNull(services, nameof(services)); Guard.NotNull(configuration, nameof(configuration)); - foreach (var plugin in plugins) + foreach (var plugin in loadedPlugins) { plugin.ConfigureServices(services, configuration); } @@ -50,10 +74,41 @@ namespace Squidex.Infrastructure.Plugins { Guard.NotNull(app, nameof(app)); - foreach (var plugin in plugins.OfType()) + foreach (var plugin in loadedPlugins.OfType()) { plugin.Configure(app); } } + + public void Log(ISemanticLog log) + { + Guard.NotNull(log, nameof(log)); + + if (loadedPlugins.Count > 0 || exceptions.Count > 0) + { + var status = exceptions.Count > 0 ? "CompletedWithErrors" : "Completed"; + + log.LogInformation(w => w + .WriteProperty("action", "pluginsLoaded") + .WriteProperty("status", status) + .WriteArray("errors", e => + { + foreach (var error in exceptions) + { + e.WriteObject(x => x + .WriteProperty("plugin", error.Plugin) + .WriteProperty("action", error.Action) + .WriteException(error.Exception)); + } + }) + .WriteArray("plugins", a => + { + foreach (var plugin in loadedPlugins) + { + a.WriteValue(plugin.GetType().ToString()); + } + })); + } + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs b/src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs deleted file mode 100644 index 54e9e1566..000000000 --- a/src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs +++ /dev/null @@ -1,69 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using CoreTweet; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Squidex.Extensions.Actions.Twitter; - -namespace Squidex.Areas.Api.Controllers.Rules -{ - public sealed class TwitterController : Controller - { - private readonly TwitterOptions twitterOptions; - - public TwitterController(IOptions twitterOptions) - { - this.twitterOptions = twitterOptions.Value; - } - - public sealed class TokenRequest - { - public string PinCode { get; set; } - - public string RequestToken { get; set; } - - public string RequestTokenSecret { get; set; } - } - - [HttpGet] - [Route("rules/twitter/auth")] - public async Task Auth() - { - var session = await OAuth.AuthorizeAsync(twitterOptions.ClientId, twitterOptions.ClientSecret); - - return Ok(new - { - session.AuthorizeUri, - session.RequestToken, - session.RequestTokenSecret - }); - } - - [HttpPost] - [Route("rules/twitter/token")] - public async Task AuthComplete([FromBody] TokenRequest request) - { - var session = new OAuth.OAuthSession - { - ConsumerKey = twitterOptions.ClientId, - ConsumerSecret = twitterOptions.ClientSecret, - RequestToken = request.RequestToken, - RequestTokenSecret = request.RequestTokenSecret - }; - - var tokens = await session.GetTokensAsync(request.PinCode); - - return Ok(new - { - tokens.AccessToken, - tokens.AccessTokenSecret - }); - } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs index 18a7eeb7e..635f620ac 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs @@ -12,7 +12,6 @@ using Orleans; using Squidex.Areas.Api.Controllers.UI.Models; using Squidex.Config; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Extensions.Actions.Twitter; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; using Squidex.Pipeline; @@ -22,18 +21,16 @@ namespace Squidex.Areas.Api.Controllers.UI public sealed class UIController : ApiController { private readonly MyUIOptions uiOptions; - private readonly TwitterOptions twitterOptions; private readonly IGrainFactory grainFactory; public UIController(ICommandBus commandBus, IOptions uiOptions, - IOptions twitterOptions, IGrainFactory grainFactory) : base(commandBus) { this.uiOptions = uiOptions.Value; + this.grainFactory = grainFactory; - this.twitterOptions = twitterOptions.Value; } /// @@ -55,8 +52,6 @@ namespace Squidex.Areas.Api.Controllers.UI result.Value.Add("mapType", uiOptions.Map?.Type ?? "OSM"); result.Value.Add("mapKey", uiOptions.Map?.GoogleMaps?.Key); - result.Value.Add("supportTwitterAction", twitterOptions.IsConfigured()); - return Ok(result.Value); } diff --git a/src/Squidex/Config/Domain/AssetServices.cs b/src/Squidex/Config/Domain/AssetServices.cs index d1edf5d54..d23731ed3 100644 --- a/src/Squidex/Config/Domain/AssetServices.cs +++ b/src/Squidex/Config/Domain/AssetServices.cs @@ -29,14 +29,14 @@ namespace Squidex.Config.Domain var path = config.GetRequiredValue("assetStore:folder:path"); services.AddSingletonAs(c => new FolderAssetStore(path, c.GetRequiredService())) - .As(); + .AsOptional(); }, ["GoogleCloud"] = () => { var bucketName = config.GetRequiredValue("assetStore:googleCloud:bucket"); services.AddSingletonAs(c => new GoogleCloudAssetStore(bucketName)) - .As(); + .AsOptional(); }, ["AzureBlob"] = () => { @@ -44,7 +44,7 @@ namespace Squidex.Config.Domain var containerName = config.GetRequiredValue("assetStore:azureBlob:containerName"); services.AddSingletonAs(c => new AzureBlobAssetStore(connectionString, containerName)) - .As(); + .AsOptional(); }, ["MongoDb"] = () => { @@ -64,7 +64,7 @@ namespace Squidex.Config.Domain return new MongoGridFsAssetStore(gridFsbucket); }) - .As(); + .AsOptional(); } }); diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 8cd35728e..3bf99f5a2 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -114,7 +114,7 @@ namespace Squidex.Config.Domain .As>(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddCommandPipeline(); services.AddBackupHandlers(); diff --git a/src/Squidex/Config/Domain/EventStoreServices.cs b/src/Squidex/Config/Domain/EventStoreServices.cs index 03cb235e2..94bebf145 100644 --- a/src/Squidex/Config/Domain/EventStoreServices.cs +++ b/src/Squidex/Config/Domain/EventStoreServices.cs @@ -39,7 +39,7 @@ namespace Squidex.Config.Domain return new MongoEventStore(mongDatabase, c.GetRequiredService()); }) - .As(); + .AsOptional(); }, ["GetEventStore"] = () => { @@ -53,7 +53,7 @@ namespace Squidex.Config.Domain .As(); services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService(), eventStorePrefix, eventStoreProjectionHost)) - .As(); + .AsOptional(); services.AddHealthChecks() .AddCheck("EventStore", tags: new[] { "node" }); diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index a623e3650..9ae8bf331 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -62,10 +62,10 @@ namespace Squidex.Config.Domain .As(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() .As(); diff --git a/src/Squidex/Config/Domain/LoggingServices.cs b/src/Squidex/Config/Domain/LoggingServices.cs index 92885e424..dfc45d04c 100644 --- a/src/Squidex/Config/Domain/LoggingServices.cs +++ b/src/Squidex/Config/Domain/LoggingServices.cs @@ -62,7 +62,7 @@ namespace Squidex.Config.Domain .As(); services.AddSingletonAs() - .As(); + .AsOptional(); } } } diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index 8d79f0ec5..bf077d755 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -78,32 +78,32 @@ namespace Squidex.Config.Domain .As(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As>(); + .AsOptional>(); services.AddSingletonAs() - .As>() - .As(); + .AsOptional>() + .AsOptional(); services.AddSingletonAs() - .As() - .As>(); + .AsOptional() + .AsOptional>(); services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetRequiredService(), c.GetRequiredService())) - .As() - .As>() - .As(); + .AsOptional() + .AsOptional>() + .AsOptional(); } }); diff --git a/src/Squidex/Config/Domain/SubscriptionServices.cs b/src/Squidex/Config/Domain/SubscriptionServices.cs index f9d6ca1a1..2d2a0e706 100644 --- a/src/Squidex/Config/Domain/SubscriptionServices.cs +++ b/src/Squidex/Config/Domain/SubscriptionServices.cs @@ -23,13 +23,13 @@ namespace Squidex.Config.Domain services.AddSingletonAs(c => c.GetRequiredService>()?.Value?.Plans.OrEmpty()); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); services.AddSingletonAs() - .As(); + .AsOptional(); } } } diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs index f2397b7ed..f89cf24cc 100644 --- a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -6,13 +6,15 @@ // ========================================================================== using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; using McMaster.NETCore.Plugins; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Extensions; +using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Plugins; namespace Squidex.Pipeline.Plugins @@ -29,50 +31,94 @@ namespace Squidex.Pipeline.Plugins if (options.Plugins != null) { - foreach (var pluginPath in options.Plugins) + foreach (var path in options.Plugins) { - PluginLoader plugin = null; - - if (pluginPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) - { - plugin = PluginLoader.CreateFromAssemblyFile(pluginPath, SharedTypes); - } - else - { - plugin = PluginLoader.CreateFromConfigFile(pluginPath, SharedTypes); - } + var plugin = LoadPlugin(path); if (plugin != null) { - var pluginAssembly = plugin.LoadDefaultAssembly(); + try + { + var pluginAssembly = plugin.LoadDefaultAssembly(); - AddParts(mvcBuilder, pluginAssembly); + AddParts(mvcBuilder, pluginAssembly); - var relatedAssemblies = pluginAssembly.GetCustomAttributes(); + foreach (var relatedAssembly in RelatedAssemblyAttribute.GetRelatedAssemblies(pluginAssembly, false)) + { + AddParts(mvcBuilder, relatedAssembly); + } - foreach (var relatedAssembly in relatedAssemblies) + pluginManager.Add(path, pluginAssembly); + } + catch (Exception ex) { - var assembly = plugin.LoadAssembly(relatedAssembly.AssemblyFileName); - - AddParts(mvcBuilder, assembly); + pluginManager.LogException(path, "LoadingAssembly", ex); } - - pluginManager.Add(pluginAssembly); + } + else + { + pluginManager.LogException(path, "LoadingPlugin", new FileNotFoundException($"Cannot find plugin at {path}")); } } } - pluginManager.Add(SquidexExtensions.Assembly); pluginManager.ConfigureServices(mvcBuilder.Services, configuration); mvcBuilder.Services.AddSingleton(pluginManager); } + private static PluginLoader LoadPlugin(string pluginPath) + { + var fullPath = GetPaths(pluginPath); + + foreach (var candidate in GetPaths(pluginPath)) + { + if (candidate.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + { + return PluginLoader.CreateFromAssemblyFile(candidate.FullName, SharedTypes); + } + + if (candidate.Extension.Equals(".json", StringComparison.OrdinalIgnoreCase)) + { + return PluginLoader.CreateFromConfigFile(candidate.FullName, SharedTypes); + } + } + + return null; + } + + private static IEnumerable GetPaths(string pluginPath) + { + var candidate = new FileInfo(Path.GetFullPath(pluginPath)); + + if (candidate.Exists) + { + yield return candidate; + } + + if (!Path.IsPathRooted(pluginPath)) + { + candidate = new FileInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), pluginPath)); + + if (candidate.Exists) + { + yield return candidate; + } + } + } + public static void UsePlugins(this IApplicationBuilder app) { var pluginManager = app.ApplicationServices.GetRequiredService(); pluginManager.Configure(app); + + var log = app.ApplicationServices.GetService(); + + if (log != null) + { + pluginManager.Log(log); + } } private static void AddParts(IMvcBuilder mvcBuilder, Assembly assembly) diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 0cab6d9a6..39b41835a 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -61,9 +61,7 @@ - - @@ -96,7 +94,6 @@ - diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 486c5ea25..a1088d534 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -78,8 +78,6 @@ namespace Squidex config.GetSection("translations:deepL")); services.Configure( config.GetSection("mode")); - services.Configure( - config.GetSection("twitter")); services.Configure( config.GetSection("robots")); services.Configure( @@ -100,9 +98,10 @@ namespace Squidex services.Configure( config.GetSection("news")); + services.AddHostedService(); + var provider = services.AddAndBuildOrleans(configuration, afterServices => { - afterServices.AddHostedService(); afterServices.AddHostedService(); }); diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 8ed70e2bc..8a642f33c 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -12,18 +12,25 @@ */ "baseUrl": "http://localhost:5000", - /*7 + /* * Set it to true to redirect the user from http to https permanently. */ "enforceHttps": false }, + /* + * Define optional paths to plugins. + */ + "plugins": [ + "Squidex.Extensions.dll" + ], + "etags": { /* * Set to true, to use strong etags. */ - "strong": false - }, + "strong": false +}, "ui": { /* @@ -313,17 +320,6 @@ "privacyUrl": "https://squidex.io/privacy" }, - "twitter": { - /* - * The client id for twitter. - */ - "clientId": "QZhb3HQcGCvE6G8yNNP9ksNet", - /* - * The client secret for twitter. - */ - "clientSecret": "Pdu9wdN72T33KJRFdFy1w4urBKDRzIyuKpc0OItQC2E616DuZD" - }, - "news": { /* * The app name where the news are stored. From de88cc82dbafd93fa34bdde28e75fe690a8e3467 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Wed, 20 Feb 2019 21:50:06 +0100 Subject: [PATCH 05/55] Small bugfixes. --- .../SquidexCoreOperations.cs | 16 +++++++++++++++ .../Plugins/IWebPlugin.cs | 2 +- .../Converters/RuleTriggerDtoFactory.cs | 2 +- src/Squidex/Config/Web/WebServices.cs | 6 +++--- .../Pipeline/Plugins/PluginExtensions.cs | 20 ++++++++++++++++--- src/Squidex/WebStartup.cs | 3 ++- .../angular/forms/control-errors.component.ts | 6 +++++- 7 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/SquidexCoreOperations.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/SquidexCoreOperations.cs b/src/Squidex.Domain.Apps.Core.Operations/SquidexCoreOperations.cs new file mode 100644 index 000000000..51c11b47d --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/SquidexCoreOperations.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Reflection; + +namespace Squidex.Domain.Apps.Core +{ + public static class SquidexCoreOperations + { + public static readonly Assembly Assembly = typeof(SquidexCoreOperations).Assembly; + } +} diff --git a/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs index d8cab06d5..f93e3eadb 100644 --- a/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs +++ b/src/Squidex.Infrastructure/Plugins/IWebPlugin.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Builder; namespace Squidex.Infrastructure.Plugins { - public interface IWebPlugin : I + public interface IWebPlugin : IPlugin { void Configure(IApplicationBuilder app); } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs index 2b9bd6328..e0d44e0e4 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs @@ -33,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters public RuleTriggerDto Visit(SchemaChangedTrigger trigger) { - return SimpleMapper.Map(trigger, new AssetChangedRuleTriggerDto()); + return SimpleMapper.Map(trigger, new SchemaChangedRuleTriggerDto()); } public RuleTriggerDto Visit(UsageTrigger trigger) diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 315f01547..3bafa44be 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -18,7 +18,7 @@ namespace Squidex.Config.Web { public static class WebServices { - public static void AddMyMvc(this IServiceCollection services, IConfiguration config) + public static void AddMyMvcWithPlugins(this IServiceCollection services, IConfiguration config) { services.AddSingletonAs() .AsSelf(); @@ -50,8 +50,8 @@ namespace Squidex.Config.Web options.Filters.Add(); options.Filters.Add(); }) - .AddMySerializers() - .AddMyPlugins(config); + .AddMyPlugins(config) + .AddMySerializers(); services.AddCors(); services.AddRouting(); diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs index f89cf24cc..507d285af 100644 --- a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -14,6 +14,10 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Plugins; @@ -21,9 +25,17 @@ namespace Squidex.Pipeline.Plugins { public static class PluginExtensions { - private static readonly Type[] SharedTypes = { typeof(IPlugin) }; - - public static void AddMyPlugins(this IMvcBuilder mvcBuilder, IConfiguration configuration) + private static readonly Type[] SharedTypes = + { + typeof(IPlugin), + typeof(SquidexCoreModel), + typeof(SquidexCoreOperations), + typeof(SquidexEntities), + typeof(SquidexEvents), + typeof(SquidexInfrastructure) + }; + + public static IMvcBuilder AddMyPlugins(this IMvcBuilder mvcBuilder, IConfiguration configuration) { var pluginManager = new PluginManager(); @@ -65,6 +77,8 @@ namespace Squidex.Pipeline.Plugins pluginManager.ConfigureServices(mvcBuilder.Services, configuration); mvcBuilder.Services.AddSingleton(pluginManager); + + return mvcBuilder; } private static PluginLoader LoadPlugin(string pluginPath) diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index a1088d534..0c29f400d 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -54,6 +54,8 @@ namespace Squidex services.AddMemoryCache(); services.AddOptions(); + services.AddMyMvcWithPlugins(config); + services.AddMyAssetServices(config); services.AddMyAuthentication(config); services.AddMyEntitiesServices(config); @@ -63,7 +65,6 @@ namespace Squidex services.AddMyInfrastructureServices(config); services.AddMyLoggingServices(config); services.AddMyMigrationServices(); - services.AddMyMvc(config); services.AddMyRuleServices(); services.AddMySerializers(); services.AddMyStoreServices(config); diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index 3a347ad06..0f8533abb 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -61,6 +61,10 @@ export class ControlErrorsComponent extends StatefulComponent implements public ngOnDestroy() { super.ngOnDestroy(); + this.unsubscribe(); + } + + private unsubscribe() { if (this.control && this.originalMarkAsTouched) { this.control['markAsTouched'] = this.originalMarkAsTouched; } @@ -86,7 +90,7 @@ export class ControlErrorsComponent extends StatefulComponent implements } if (this.control !== control) { - this.ngOnDestroy(); + this.unsubscribe(); this.control = control; From 50d919aec51143d60bdeccd4dbc40eaae24d8da5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 21 Feb 2019 17:39:24 +0100 Subject: [PATCH 06/55] Less hacks. --- .../Actions/Algolia/AlgoliaAction.cs | 1 - .../Actions/Algolia/AlgoliaPlugin.cs | 3 +- .../Actions/AzureQueue/AzureQueueAction.cs | 1 - .../Actions/AzureQueue/AzureQueuePlugin.cs | 3 +- .../Actions/Discourse/DiscourseAction.cs | 1 - .../Actions/Discourse/DiscoursePlugin.cs | 3 +- .../ElasticSearch/ElasticSearchAction.cs | 1 - .../ElasticSearch/ElasticSearchPlugin.cs | 3 +- .../Actions/Email/EmailAction.cs | 1 - .../Actions/Email/EmailPlugin.cs | 3 +- .../Actions/Fastly/FastlyAction.cs | 1 - .../Actions/Fastly/FastlyPlugin.cs | 3 +- .../Actions/Medium/MediumAction.cs | 1 - .../Actions/Medium/MediumPlugin.cs | 3 +- .../Actions/Prerender/PrerenderAction.cs | 1 - .../Actions/Prerender/PrerenderPlugin.cs | 3 +- .../Actions/Slack/SlackAction.cs | 1 - .../Actions/Slack/SlackPlugin.cs | 3 +- .../Actions/Twitter/TweetAction.cs | 1 - .../Actions/Twitter/TwitterPlugin.cs | 3 +- .../Actions/Webhook/WebhookAction.cs | 1 - .../Actions/Webhook/WebhookPlugin.cs | 3 +- .../Schemas/FieldRegistry.cs | 6 +- .../SquidexCoreModel.cs | 2 +- .../DependencyInjectionExtensions.cs | 25 +++ .../HandleRules/RuleActionHandlerAttribute.cs | 30 --- .../HandleRules/RuleActionRegistration.cs | 24 +++ .../HandleRules/RuleActionRegistry.cs | 175 ------------------ .../HandleRules/RuleRegistry.cs | 140 +++++++++++++- .../SquidexEvents.cs | 2 +- .../States/MongoSnapshotStore.cs | 9 +- .../AutoAssembyTypeProvider.cs | 17 ++ .../Configuration/ConfigurationExtensions.cs | 4 +- .../Configuration/Options.cs | 2 +- .../DependencyInjectionExtensions.cs | 4 +- src/Squidex.Infrastructure/ITypeProvider.cs | 14 ++ .../Log/ApplicationInfoLogAppender.cs | 2 +- .../Reflection/ReflectionExtensions.cs | 2 +- .../SquidexInfrastructure.cs | 2 +- .../TypeNameRegistry.cs | 22 ++- .../Api/Config/Swagger/SwaggerServices.cs | 1 - .../Api/Config/Swagger/XmlTagProcessor.cs | 2 +- .../Rules/Models/RuleActionConverter.cs | 4 +- .../Rules/Models/RuleActionProcessor.cs | 16 +- .../Api/Controllers/Rules/RulesController.cs | 11 +- .../Api/Controllers/Users/UsersController.cs | 3 +- .../Config/IdentityServerServices.cs | 3 +- src/Squidex/Config/Domain/AssetServices.cs | 2 - src/Squidex/Config/Domain/EntitiesServices.cs | 2 - .../Config/Domain/EventPublishersServices.cs | 1 - .../Config/Domain/EventStoreServices.cs | 12 +- .../Config/Domain/InfrastructureServices.cs | 1 - src/Squidex/Config/Domain/LoggingServices.cs | 1 - src/Squidex/Config/Domain/RuleServices.cs | 10 +- .../Config/Domain/SerializationInitializer.cs | 41 ++++ .../Config/Domain/SerializationServices.cs | 68 ++++--- src/Squidex/Config/Domain/StoreServices.cs | 27 ++- .../Config/Domain/SubscriptionServices.cs | 1 - src/Squidex/Config/Orleans/OrleansServices.cs | 1 - src/Squidex/Config/Web/WebServices.cs | 1 - src/Squidex/Pipeline/Squid/SquidMiddleware.cs | 3 +- src/Squidex/Pipeline/Swagger/NSwagHelper.cs | 3 +- src/Squidex/WebStartup.cs | 1 - ...egistry.cs => RuleElementRegistryTests.cs} | 13 +- .../TestUtils.cs | 8 +- .../TypeNameRegistryTests.cs | 41 +++- tools/Migrate_01/SquidexMigrations.cs | 2 +- 67 files changed, 449 insertions(+), 356 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs delete mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs create mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistration.cs delete mode 100644 src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs create mode 100644 src/Squidex.Infrastructure/AutoAssembyTypeProvider.cs create mode 100644 src/Squidex.Infrastructure/ITypeProvider.cs create mode 100644 src/Squidex/Config/Domain/SerializationInitializer.cs rename tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/{RuleElementRegistry.cs => RuleElementRegistryTests.cs} (93%) diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs index c6952b152..eb6c041a4 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Algolia { - [RuleActionHandler(typeof(AlgoliaActionHandler))] [RuleAction( IconImage = "", IconColor = "#0d9bf9", diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs index 71d0f4c08..9ef4538c9 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Algolia @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Algolia { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index 45f176d9f..41c5be7a9 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs @@ -14,7 +14,6 @@ using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.AzureQueue { - [RuleActionHandler(typeof(AzureQueueActionHandler))] [RuleAction( IconImage = "", IconColor = "#0d9bf9", diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs index e069c08d2..43c84e857 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueuePlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.AzureQueue @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.AzureQueue { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index 97f6b6538..295c7f683 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs @@ -13,7 +13,6 @@ using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Discourse { - [RuleActionHandler(typeof(DiscourseActionHandler))] [RuleAction( IconImage = "", IconColor = "#eB6121", diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs index 2d7d9f6d7..fb1105657 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscoursePlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Discourse @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Discourse { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 00b4a74db..298e4ad66 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -13,7 +13,6 @@ using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.ElasticSearch { - [RuleActionHandler(typeof(ElasticSearchActionHandler))] [RuleAction( IconImage = "", IconColor = "#1e5470", diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs index 49cf06697..50bdf12d4 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.ElasticSearch @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index be24d3166..c801e629b 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Email { - [RuleActionHandler(typeof(EmailActionHandler))] [RuleAction( IconImage = "", IconColor = "#333300", diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs b/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs index 34a5c88ce..aeed28445 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Email @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Email { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs index e4e056ba7..d227e8ce1 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Fastly { - [RuleActionHandler(typeof(FastlyActionHandler))] [RuleAction( IconImage = "", IconColor = "#e23335", diff --git a/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs b/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs index 8268d912a..f6802dde3 100644 --- a/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Fastly/FastlyPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Fastly @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Fastly { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 65eddcefd..3c6ce25e2 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Medium { - [RuleActionHandler(typeof(MediumActionHandler))] [RuleAction( IconImage = "", IconColor = "#00ab6c", diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs index 8a1090672..b2664ac3b 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Medium @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Medium { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs index 0978a79a4..44285379f 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Prerender { - [RuleActionHandler(typeof(PrerenderActionHandler))] [RuleAction( IconImage = "", IconColor = "#2c3e50", diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs index 89e1a6c9b..fcf623e3f 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Prerender @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Prerender { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs index 47e1dddc7..9b1382530 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackAction.cs @@ -13,7 +13,6 @@ using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Slack { - [RuleActionHandler(typeof(SlackActionHandler))] [RuleAction( IconImage = "", IconColor = "#5c3a58", diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs index 1149a5875..f0463c87e 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Slack @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Slack { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index c23b3dd98..5f68d9fd4 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Extensions.Actions.Twitter { - [RuleActionHandler(typeof(TweetActionHandler))] [RuleAction( IconImage = "", IconColor = "#1da1f2", diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs index 911db17d3..fd59ec980 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TwitterPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Twitter @@ -19,7 +18,7 @@ namespace Squidex.Extensions.Actions.Twitter services.Configure( configuration.GetSection("twitter")); - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index cdd50c429..2cf8e339a 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -13,7 +13,6 @@ using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Webhook { - [RuleActionHandler(typeof(WebhookActionHandler))] [RuleAction( IconImage = "", IconColor = "#4bb958", diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs index c9786cdf0..d729f1b5e 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookPlugin.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Infrastructure.Plugins; namespace Squidex.Extensions.Actions.Webhook @@ -16,7 +15,7 @@ namespace Squidex.Extensions.Actions.Webhook { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { - RuleActionRegistry.Add(); + services.AddRuleAction(); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs index f4049da06..6154cff13 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs @@ -12,12 +12,12 @@ using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Schemas { - public static class FieldRegistry + public class FieldRegistry : ITypeProvider { private const string Suffix = "Properties"; private const string SuffixOld = "FieldProperties"; - public static TypeNameRegistry MapFields(this TypeNameRegistry typeNameRegistry) + public void Map(TypeNameRegistry typeNameRegistry) { var types = typeof(FieldRegistry).Assembly.GetTypes().Where(x => typeof(FieldProperties).IsAssignableFrom(x) && !x.IsAbstract); @@ -32,8 +32,6 @@ namespace Squidex.Domain.Apps.Core.Schemas typeNameRegistry.MapObsolete(type, type.TypeName(false, SuffixOld)); } } - - return typeNameRegistry; } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs b/src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs index 6f4f99495..860a9421a 100644 --- a/src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/SquidexCoreModel.cs @@ -9,7 +9,7 @@ using System.Reflection; namespace Squidex.Domain.Apps.Core { - public static class SquidexCoreModel + public sealed class SquidexCoreModel { public static readonly Assembly Assembly = typeof(SquidexCoreModel).Assembly; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs new file mode 100644 index 000000000..1113bcfbb --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/DependencyInjectionExtensions.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class DependencyInjectionExtensions + { + public static IServiceCollection AddRuleAction(this IServiceCollection services) where THandler : class, IRuleActionHandler where TAction : RuleAction + { + services.AddSingletonAs() + .As(); + + services.AddSingleton(new RuleActionRegistration(typeof(TAction))); + + return services; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs deleted file mode 100644 index 24d29ccea..000000000 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandlerAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Core.HandleRules -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public sealed class RuleActionHandlerAttribute : Attribute - { - public Type HandlerType { get; } - - public RuleActionHandlerAttribute(Type handlerType) - { - Guard.NotNull(handlerType, nameof(handlerType)); - - HandlerType = handlerType; - - if (!typeof(IRuleActionHandler).IsAssignableFrom(handlerType)) - { - throw new ArgumentException($"Handler type must implement {typeof(IRuleActionHandler)}.", nameof(handlerType)); - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistration.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistration.cs new file mode 100644 index 000000000..2d0477228 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistration.cs @@ -0,0 +1,24 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Squidex.Infrastructure; + +namespace Squidex.Domain.Apps.Core.HandleRules +{ + public sealed class RuleActionRegistration + { + public Type ActionType { get; } + + internal RuleActionRegistration(Type actionType) + { + Guard.NotNull(actionType, nameof(actionType)); + + ActionType = actionType; + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs deleted file mode 100644 index c1eebfb1e..000000000 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionRegistry.cs +++ /dev/null @@ -1,175 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Reflection; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Core.HandleRules -{ - public static class RuleActionRegistry - { - private const string ActionSuffix = "Action"; - private const string ActionSuffixV2 = "ActionV2"; - private static readonly HashSet ActionHandlerTypes = new HashSet(); - private static readonly Dictionary ActionTypes = new Dictionary(); - - public static IReadOnlyDictionary Actions - { - get { return ActionTypes; } - } - - public static IReadOnlyCollection ActionHandlers - { - get { return ActionHandlerTypes; } - } - - static RuleActionRegistry() - { - var actionTypes = - typeof(RuleActionRegistry).Assembly - .GetTypes() - .Where(x => typeof(RuleAction).IsAssignableFrom(x)) - .Where(x => x.GetCustomAttribute() != null) - .Where(x => x.GetCustomAttribute() != null) - .ToList(); - - foreach (var actionType in actionTypes) - { - Add(actionType); - } - } - - public static void Add() where T : RuleAction - { - Add(typeof(T)); - } - - private static void Add(Type actionType) - { - var metadata = actionType.GetCustomAttribute(); - - if (metadata == null) - { - return; - } - - var handlerAttribute = actionType.GetCustomAttribute(); - - if (handlerAttribute == null) - { - return; - } - - var name = GetActionName(actionType); - - var definition = - new RuleActionDefinition - { - Type = actionType, - Display = metadata.Display, - Description = metadata.Description, - IconColor = metadata.IconColor, - IconImage = metadata.IconImage, - ReadMore = metadata.ReadMore - }; - - foreach (var property in actionType.GetProperties()) - { - if (property.CanRead && property.CanWrite) - { - var actionProperty = new RuleActionProperty { Name = property.Name.ToCamelCase(), Display = property.Name }; - - var display = property.GetCustomAttribute(); - - if (!string.IsNullOrWhiteSpace(display?.Name)) - { - actionProperty.Display = display.Name; - } - - if (!string.IsNullOrWhiteSpace(display?.Description)) - { - actionProperty.Description = display.Description; - } - - var type = property.PropertyType; - - if ((property.GetCustomAttribute() != null || (type.IsValueType && !IsNullable(type))) && type != typeof(bool) && type != typeof(bool?)) - { - actionProperty.IsRequired = true; - } - - if (property.GetCustomAttribute() != null) - { - actionProperty.IsFormattable = true; - } - - var dataType = property.GetCustomAttribute()?.DataType; - - if (type == typeof(bool) || type == typeof(bool?)) - { - actionProperty.Editor = RuleActionPropertyEditor.Checkbox; - } - else if (type == typeof(int) || type == typeof(int?)) - { - actionProperty.Editor = RuleActionPropertyEditor.Number; - } - else if (dataType == DataType.Url) - { - actionProperty.Editor = RuleActionPropertyEditor.Url; - } - else if (dataType == DataType.Password) - { - actionProperty.Editor = RuleActionPropertyEditor.Password; - } - else if (dataType == DataType.EmailAddress) - { - actionProperty.Editor = RuleActionPropertyEditor.Email; - } - else if (dataType == DataType.MultilineText) - { - actionProperty.Editor = RuleActionPropertyEditor.TextArea; - } - else - { - actionProperty.Editor = RuleActionPropertyEditor.Text; - } - - definition.Properties.Add(actionProperty); - } - } - - ActionTypes[name] = definition; - - ActionHandlerTypes.Add(actionType.GetCustomAttribute().HandlerType); - } - - private static bool IsNullable(Type type) - { - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - public static TypeNameRegistry MapRuleActions(this TypeNameRegistry typeNameRegistry) - { - foreach (var actionType in ActionTypes.Values) - { - typeNameRegistry.Map(actionType.Type, actionType.Type.Name); - } - - return typeNameRegistry; - } - - private static string GetActionName(Type type) - { - return type.TypeName(false, ActionSuffix, ActionSuffixV2); - } - } -} diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs index 31228cadd..ac9789a9f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleRegistry.cs @@ -7,17 +7,151 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; +#pragma warning disable RECS0033 // Convert 'if' to '||' expression + namespace Squidex.Domain.Apps.Core.HandleRules { - public static class RuleRegistry + public sealed class RuleRegistry : ITypeProvider { - public static TypeNameRegistry MapRules(this TypeNameRegistry typeNameRegistry) + private const string ActionSuffix = "Action"; + private const string ActionSuffixV2 = "ActionV2"; + private readonly Dictionary actionTypes = new Dictionary(); + + public IReadOnlyDictionary Actions { + get { return actionTypes; } + } + + public RuleRegistry(IEnumerable registrations = null) + { + if (registrations != null) + { + foreach (var registration in registrations) + { + Add(registration.ActionType); + } + } + } + + public void Add() where T : RuleAction + { + Add(typeof(T)); + } + + private void Add(Type actionType) + { + var metadata = actionType.GetCustomAttribute(); + + if (metadata == null) + { + return; + } + + var name = GetActionName(actionType); + + var definition = + new RuleActionDefinition + { + Type = actionType, + Display = metadata.Display, + Description = metadata.Description, + IconColor = metadata.IconColor, + IconImage = metadata.IconImage, + ReadMore = metadata.ReadMore + }; + + foreach (var property in actionType.GetProperties()) + { + if (property.CanRead && property.CanWrite) + { + var actionProperty = new RuleActionProperty { Name = property.Name.ToCamelCase(), Display = property.Name }; + + var display = property.GetCustomAttribute(); + + if (!string.IsNullOrWhiteSpace(display?.Name)) + { + actionProperty.Display = display.Name; + } + + if (!string.IsNullOrWhiteSpace(display?.Description)) + { + actionProperty.Description = display.Description; + } + + var type = property.PropertyType; + + if ((property.GetCustomAttribute() != null || (type.IsValueType && !IsNullable(type))) && type != typeof(bool) && type != typeof(bool?)) + { + actionProperty.IsRequired = true; + } + + if (property.GetCustomAttribute() != null) + { + actionProperty.IsFormattable = true; + } + + var dataType = property.GetCustomAttribute()?.DataType; + + if (type == typeof(bool) || type == typeof(bool?)) + { + actionProperty.Editor = RuleActionPropertyEditor.Checkbox; + } + else if (type == typeof(int) || type == typeof(int?)) + { + actionProperty.Editor = RuleActionPropertyEditor.Number; + } + else if (dataType == DataType.Url) + { + actionProperty.Editor = RuleActionPropertyEditor.Url; + } + else if (dataType == DataType.Password) + { + actionProperty.Editor = RuleActionPropertyEditor.Password; + } + else if (dataType == DataType.EmailAddress) + { + actionProperty.Editor = RuleActionPropertyEditor.Email; + } + else if (dataType == DataType.MultilineText) + { + actionProperty.Editor = RuleActionPropertyEditor.TextArea; + } + else + { + actionProperty.Editor = RuleActionPropertyEditor.Text; + } + + definition.Properties.Add(actionProperty); + } + } + + actionTypes[name] = definition; + } + + private static bool IsNullable(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + private static string GetActionName(Type type) + { + return type.TypeName(false, ActionSuffix, ActionSuffixV2); + } + + public void Map(TypeNameRegistry typeNameRegistry) + { + foreach (var actionType in actionTypes.Values) + { + typeNameRegistry.Map(actionType.Type, actionType.Type.Name); + } + var eventTypes = typeof(EnrichedEvent).Assembly.GetTypes().Where(x => typeof(EnrichedEvent).IsAssignableFrom(x) && !x.IsAbstract); var addedTypes = new HashSet(); @@ -39,8 +173,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules typeNameRegistry.Map(type, type.Name); } } - - return typeNameRegistry; } } } diff --git a/src/Squidex.Domain.Apps.Events/SquidexEvents.cs b/src/Squidex.Domain.Apps.Events/SquidexEvents.cs index d49cb8921..7f85deb57 100644 --- a/src/Squidex.Domain.Apps.Events/SquidexEvents.cs +++ b/src/Squidex.Domain.Apps.Events/SquidexEvents.cs @@ -9,7 +9,7 @@ using System.Reflection; namespace Squidex.Domain.Apps.Events { - public static class SquidexEvents + public sealed class SquidexEvents { public static readonly Assembly Assembly = typeof(SquidexEvents).Assembly; } diff --git a/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs b/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs index 3f0a356b1..2b6cd2643 100644 --- a/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs @@ -5,10 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Linq; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; +using Newtonsoft.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.MongoDb; @@ -16,9 +18,12 @@ namespace Squidex.Infrastructure.States { public class MongoSnapshotStore : MongoRepositoryBase>, ISnapshotStore { - public MongoSnapshotStore(IMongoDatabase database) + public MongoSnapshotStore(IMongoDatabase database, JsonSerializer jsonSerializer) : base(database) { + Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); + + BsonJsonConvention.Register(jsonSerializer); } protected override string CollectionName() @@ -55,7 +60,7 @@ namespace Squidex.Infrastructure.States } } - public async Task ReadAllAsync(System.Func callback) + public async Task ReadAllAsync(Func callback) { using (Profiler.TraceMethod>()) { diff --git a/src/Squidex.Infrastructure/AutoAssembyTypeProvider.cs b/src/Squidex.Infrastructure/AutoAssembyTypeProvider.cs new file mode 100644 index 000000000..1cb4f5ee5 --- /dev/null +++ b/src/Squidex.Infrastructure/AutoAssembyTypeProvider.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure +{ + public sealed class AutoAssembyTypeProvider : ITypeProvider + { + public void Map(TypeNameRegistry typeNameRegistry) + { + typeNameRegistry.MapUnmapped(typeof(T).Assembly); + } + } +} diff --git a/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs index 2194f753b..62d0521a4 100644 --- a/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs +++ b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs @@ -7,9 +7,9 @@ using System.Globalization; using System.Linq; -using Microsoft.Extensions.Configuration; +using Squidex.Infrastructure; -namespace Squidex.Infrastructure.Configuration +namespace Microsoft.Extensions.Configuration { public static class ConfigurationExtensions { diff --git a/src/Squidex.Infrastructure/Configuration/Options.cs b/src/Squidex.Infrastructure/Configuration/Options.cs index 79a3cdfae..89cb9f596 100644 --- a/src/Squidex.Infrastructure/Configuration/Options.cs +++ b/src/Squidex.Infrastructure/Configuration/Options.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; -namespace Squidex.Infrastructure.Configuration +namespace Microsoft.Extensions.Configuration { public sealed class Options : Dictionary { diff --git a/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs index 5b39ad14b..da40d75eb 100644 --- a/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs +++ b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs @@ -7,10 +7,10 @@ using System; using System.Linq; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Squidex.Infrastructure; -namespace Squidex.Infrastructure.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection { public static class DependencyInjectionExtensions { diff --git a/src/Squidex.Infrastructure/ITypeProvider.cs b/src/Squidex.Infrastructure/ITypeProvider.cs new file mode 100644 index 000000000..63ee58238 --- /dev/null +++ b/src/Squidex.Infrastructure/ITypeProvider.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure +{ + public interface ITypeProvider + { + void Map(TypeNameRegistry typeNameRegistry); + } +} diff --git a/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs b/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs index 9763aabf8..9d13c43b8 100644 --- a/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs +++ b/src/Squidex.Infrastructure/Log/ApplicationInfoLogAppender.cs @@ -17,7 +17,7 @@ namespace Squidex.Infrastructure.Log private readonly string applicationSessionId; public ApplicationInfoLogAppender(Type type, Guid applicationSession) - : this(type?.GetTypeInfo().Assembly, applicationSession) + : this(type?.Assembly, applicationSession) { } diff --git a/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs b/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs index 474f55355..55354eb13 100644 --- a/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs +++ b/src/Squidex.Infrastructure/Reflection/ReflectionExtensions.cs @@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.Reflection BindingFlags.Public | BindingFlags.Instance; - if (!type.GetTypeInfo().IsInterface) + if (!type.IsInterface) { return type.GetProperties(bindingFlags); } diff --git a/src/Squidex.Infrastructure/SquidexInfrastructure.cs b/src/Squidex.Infrastructure/SquidexInfrastructure.cs index 8dac58f91..20444d138 100644 --- a/src/Squidex.Infrastructure/SquidexInfrastructure.cs +++ b/src/Squidex.Infrastructure/SquidexInfrastructure.cs @@ -9,7 +9,7 @@ using System.Reflection; namespace Squidex.Infrastructure { - public static class SquidexInfrastructure + public sealed class SquidexInfrastructure { public static readonly Assembly Assembly = typeof(SquidexInfrastructure).Assembly; } diff --git a/src/Squidex.Infrastructure/TypeNameRegistry.cs b/src/Squidex.Infrastructure/TypeNameRegistry.cs index 233f5ee28..5df5f9f00 100644 --- a/src/Squidex.Infrastructure/TypeNameRegistry.cs +++ b/src/Squidex.Infrastructure/TypeNameRegistry.cs @@ -16,6 +16,17 @@ namespace Squidex.Infrastructure private readonly Dictionary namesByType = new Dictionary(); private readonly Dictionary typesByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + public TypeNameRegistry(IEnumerable providers = null) + { + if (providers != null) + { + foreach (var provider in providers) + { + Map(provider); + } + } + } + public TypeNameRegistry MapObsolete(Type type, string name) { Guard.NotNull(type, nameof(type)); @@ -36,11 +47,20 @@ namespace Squidex.Infrastructure return this; } + public TypeNameRegistry Map(ITypeProvider provider) + { + Guard.NotNull(provider, nameof(provider)); + + provider.Map(this); + + return this; + } + public TypeNameRegistry Map(Type type) { Guard.NotNull(type, nameof(type)); - var typeNameAttribute = type.GetTypeInfo().GetCustomAttribute(); + var typeNameAttribute = type.GetCustomAttribute(); if (!string.IsNullOrWhiteSpace(typeNameAttribute?.TypeName)) { diff --git a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs index 07a973ea4..ddccd616c 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs @@ -15,7 +15,6 @@ using NSwag.SwaggerGeneration.Processors; using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Infrastructure; -using Squidex.Infrastructure.DependencyInjection; namespace Squidex.Areas.Api.Config.Swagger { diff --git a/src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs index 01dc1c73b..b0ecdf36d 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/XmlTagProcessor.cs @@ -22,7 +22,7 @@ namespace Squidex.Areas.Api.Config.Swagger { foreach (var controllerType in context.ControllerTypes) { - var attribute = controllerType.GetTypeInfo().GetCustomAttribute(); + var attribute = controllerType.GetCustomAttribute(); if (attribute != null) { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs index b36243b4f..50b4446e0 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionConverter.cs @@ -7,15 +7,13 @@ using System; using System.Collections.Generic; -using System.Linq; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; namespace Squidex.Areas.Api.Controllers.Rules.Models { public sealed class RuleActionConverter : MyJsonInheritanceConverter { - private static readonly Dictionary Mapping = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type); + public static IReadOnlyDictionary Mapping { get; set; } public RuleActionConverter() : base("actionType", Mapping) diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs index b2abd5b91..b0337ed42 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs @@ -13,11 +13,21 @@ using NSwag.SwaggerGeneration.Processors; using NSwag.SwaggerGeneration.Processors.Contexts; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure; namespace Squidex.Areas.Api.Controllers.Rules.Models { public sealed class RuleActionProcessor : IDocumentProcessor { + private readonly RuleRegistry ruleRegistry; + + public RuleActionProcessor(RuleRegistry ruleRegistry) + { + Guard.NotNull(ruleRegistry, nameof(ruleRegistry)); + + this.ruleRegistry = ruleRegistry; + } + public async Task ProcessAsync(DocumentProcessorContext context) { try @@ -36,16 +46,16 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models Type = JsonObjectType.String, IsRequired = true }; - foreach (var derived in RuleActionRegistry.Actions) + foreach (var action in ruleRegistry.Actions) { - var derivedSchema = await context.SchemaGenerator.GenerateAsync(derived.Value.Type, context.SchemaResolver); + var derivedSchema = await context.SchemaGenerator.GenerateAsync(action.Value.Type, context.SchemaResolver); var oldName = context.Document.Definitions.FirstOrDefault(x => x.Value == derivedSchema).Key; if (oldName != null) { context.Document.Definitions.Remove(oldName); - context.Document.Definitions.Add(derived.Key, derivedSchema); + context.Document.Definitions.Add(action.Key, derivedSchema); } } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index d3d7edd1b..edecc7b6f 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -30,17 +30,18 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiExplorerSettings(GroupName = nameof(Rules))] public sealed class RulesController : ApiController { - private static readonly string RuleActionsEtag = string.Join(";", RuleActionRegistry.Actions.Select(x => x.Key)).Sha256Base64(); private readonly IAppProvider appProvider; private readonly IRuleEventRepository ruleEventsRepository; + private readonly RuleRegistry ruleRegistry; public RulesController(ICommandBus commandBus, IAppProvider appProvider, - IRuleEventRepository ruleEventsRepository) + IRuleEventRepository ruleEventsRepository, RuleRegistry ruleRegistry) : base(commandBus) { this.appProvider = appProvider; this.ruleEventsRepository = ruleEventsRepository; + this.ruleRegistry = ruleRegistry; } /// @@ -56,9 +57,11 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(0)] public IActionResult GetActions() { - var response = RuleActionRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); + var etag = string.Join(";", ruleRegistry.Actions.Select(x => x.Key)).Sha256Base64(); - Response.Headers[HeaderNames.ETag] = RuleActionsEtag; + var response = ruleRegistry.Actions.ToDictionary(x => x.Key, x => RuleElementDto.FromDefinition(x.Value)); + + Response.Headers[HeaderNames.ETag] = etag; return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs index 3aef01d84..ea9cc4de7 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs @@ -9,7 +9,6 @@ using System; using System.IO; using System.Linq; using System.Net.Http; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Users.Models; @@ -34,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Users static UsersController() { - var assembly = typeof(UsersController).GetTypeInfo().Assembly; + var assembly = typeof(UsersController).Assembly; using (var avatarStream = assembly.GetManifestResourceStream("Squidex.Areas.Api.Controllers.Users.Assets.Avatar.png")) { diff --git a/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index f5025efd6..ef07a13b6 100644 --- a/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Reflection; using System.Security.Cryptography.X509Certificates; using IdentityModel; using IdentityServer4.Models; @@ -29,7 +28,7 @@ namespace Squidex.Areas.IdentityServer.Config { X509Certificate2 certificate; - var assembly = typeof(IdentityServerServices).GetTypeInfo().Assembly; + var assembly = typeof(IdentityServerServices).Assembly; using (var certStream = assembly.GetManifestResourceStream("Squidex.Areas.IdentityServer.Config.Cert.IdentityCert.pfx")) { diff --git a/src/Squidex/Config/Domain/AssetServices.cs b/src/Squidex/Config/Domain/AssetServices.cs index d23731ed3..617c8ff8c 100644 --- a/src/Squidex/Config/Domain/AssetServices.cs +++ b/src/Squidex/Config/Domain/AssetServices.cs @@ -12,8 +12,6 @@ using MongoDB.Driver.GridFS; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets.ImageSharp; -using Squidex.Infrastructure.Configuration; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Log; namespace Squidex.Config.Domain diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index 3bf99f5a2..c7a215257 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -42,8 +42,6 @@ using Squidex.Domain.Apps.Entities.Schemas.Indexes; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Configuration; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Migrations; using Squidex.Pipeline; diff --git a/src/Squidex/Config/Domain/EventPublishersServices.cs b/src/Squidex/Config/Domain/EventPublishersServices.cs index 82004c1db..dad6cd6ec 100644 --- a/src/Squidex/Config/Domain/EventPublishersServices.cs +++ b/src/Squidex/Config/Domain/EventPublishersServices.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; diff --git a/src/Squidex/Config/Domain/EventStoreServices.cs b/src/Squidex/Config/Domain/EventStoreServices.cs index 94bebf145..abd77f817 100644 --- a/src/Squidex/Config/Domain/EventStoreServices.cs +++ b/src/Squidex/Config/Domain/EventStoreServices.cs @@ -11,8 +11,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Squidex.Infrastructure; -using Squidex.Infrastructure.Configuration; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Grains; @@ -47,12 +45,14 @@ namespace Squidex.Config.Domain var eventStoreProjectionHost = config.GetRequiredValue("eventStore:getEventStore:projectionHost"); var eventStorePrefix = config.GetValue("eventStore:getEventStore:prefix"); - var connection = EventStoreConnection.Create(eventStoreConfiguration); - - services.AddSingletonAs(connection) + services.AddSingletonAs(_ => EventStoreConnection.Create(eventStoreConfiguration)) .As(); - services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService(), eventStorePrefix, eventStoreProjectionHost)) + services.AddSingletonAs(c => new GetEventStore( + c.GetRequiredService(), + c.GetRequiredService(), + eventStorePrefix, + eventStoreProjectionHost)) .AsOptional(); services.AddHealthChecks() diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index 9ae8bf331..8e1b2e042 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -18,7 +18,6 @@ using Squidex.Domain.Apps.Entities.Apps.Diagnostics; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.UsageTracking; diff --git a/src/Squidex/Config/Domain/LoggingServices.cs b/src/Squidex/Config/Domain/LoggingServices.cs index dfc45d04c..59e7932e3 100644 --- a/src/Squidex/Config/Domain/LoggingServices.cs +++ b/src/Squidex/Config/Domain/LoggingServices.cs @@ -9,7 +9,6 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Domain.Apps.Entities.Apps; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Log; using Squidex.Pipeline; diff --git a/src/Squidex/Config/Domain/RuleServices.cs b/src/Squidex/Config/Domain/RuleServices.cs index 685bc2077..eb8ad2cca 100644 --- a/src/Squidex/Config/Domain/RuleServices.cs +++ b/src/Squidex/Config/Domain/RuleServices.cs @@ -12,7 +12,7 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure.DependencyInjection; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Config.Domain @@ -39,16 +39,14 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As().AsSelf(); + services.AddSingletonAs() .AsSelf(); services.AddSingletonAs() .AsSelf(); - - foreach (var actionHandler in RuleActionRegistry.ActionHandlers) - { - services.AddSingleton(typeof(IRuleActionHandler), actionHandler); - } } } } diff --git a/src/Squidex/Config/Domain/SerializationInitializer.cs b/src/Squidex/Config/Domain/SerializationInitializer.cs new file mode 100644 index 000000000..bdec8c549 --- /dev/null +++ b/src/Squidex/Config/Domain/SerializationInitializer.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Squidex.Areas.Api.Controllers.Rules.Models; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Infrastructure; +using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Config.Domain +{ + public sealed class SerializationInitializer : IInitializable + { + private readonly JsonSerializer jsonSerializer; + private readonly RuleRegistry ruleRegistry; + + public SerializationInitializer(JsonSerializer jsonSerializer, RuleRegistry ruleRegistry) + { + this.jsonSerializer = jsonSerializer; + + this.ruleRegistry = ruleRegistry; + } + + public Task InitializeAsync(CancellationToken ct = default) + { + BsonJsonConvention.Register(jsonSerializer); + + RuleActionConverter.Mapping = ruleRegistry.Actions.ToDictionary(x => x.Key, x => x.Value.Type); + + return TaskHelper.Done; + } + } +} diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index 4ef371ba4..30f1b27bf 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps.Json; -using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; @@ -24,23 +23,8 @@ namespace Squidex.Config.Domain { public static class SerializationServices { - private static readonly TypeNameRegistry TypeNameRegistry = - new TypeNameRegistry() - .MapFields() - .MapRules() - .MapRuleActions() - .MapUnmapped(SquidexCoreModel.Assembly) - .MapUnmapped(SquidexEvents.Assembly) - .MapUnmapped(SquidexInfrastructure.Assembly) - .MapUnmapped(SquidexMigrations.Assembly); - - public static readonly JsonSerializerSettings DefaultJsonSettings = new JsonSerializerSettings(); - public static readonly JsonSerializer DefaultJsonSerializer; - - private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) + private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) { - settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry); - settings.ContractResolver = new ConverterContractResolver( new AppClientsConverter(), new AppContributorsConverter(), @@ -66,22 +50,52 @@ namespace Squidex.Config.Domain settings.DateParseHandling = DateParseHandling.None; settings.TypeNameHandling = typeNameHandling; - } - - static SerializationServices() - { - ConfigureJson(DefaultJsonSettings, TypeNameHandling.Auto); - DefaultJsonSerializer = JsonSerializer.Create(DefaultJsonSettings); + return settings; } public static IServiceCollection AddMySerializers(this IServiceCollection services) { - services.AddSingleton(DefaultJsonSettings); - services.AddSingleton(DefaultJsonSerializer); - services.AddSingleton(TypeNameRegistry); + services.AddSingletonAs>() + .As(); + + services.AddSingletonAs>() + .As(); + + services.AddSingletonAs>() + .As(); + + services.AddSingletonAs>() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .AsSelf(); + + services.AddSingletonAs() + .AsSelf(); + + services.AddSingletonAs(c => JsonSerializer.Create(c.GetRequiredService())) + .AsSelf(); + + services.AddSingletonAs(c => + { + var serializerSettings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.Auto); + + var typeNameRegistry = c.GetService(); + + if (typeNameRegistry != null) + { + serializerSettings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry); + } - services.AddSingleton(new NewtonsoftJsonSerializer(DefaultJsonSettings)); + return serializerSettings; + }).As(); return services; } diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index bf077d755..a980bafc6 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -27,13 +27,10 @@ using Squidex.Domain.Users; using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Infrastructure; -using Squidex.Infrastructure.Configuration; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; using Squidex.Infrastructure.UsageTracking; @@ -47,23 +44,20 @@ namespace Squidex.Config.Domain { ["MongoDB"] = () => { - BsonJsonConvention.Register(SerializationServices.DefaultJsonSerializer); - var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration"); var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database"); var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName); - var mongoClient = Singletons.GetOrAdd(mongoConfiguration, s => new MongoClient(s)); - var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); - var mongoContentDatabase = mongoClient.GetDatabase(mongoContentDatabaseName); - services.AddSingleton(typeof(ISnapshotStore<,>), typeof(MongoSnapshotStore<,>)); - services.AddSingletonAs(mongoDatabase) + services.AddSingletonAs(c => Singletons.GetOrAdd(mongoConfiguration, s => new MongoClient(s))) + .As(); + + services.AddSingletonAs(c => c.GetRequiredService().GetDatabase(mongoDatabaseName)) .As(); - services.AddHealthChecks() - .AddCheck("MongoDB", tags: new[] { "node" }); + services.AddTransientAs(c => new DeleteContentCollections(c.GetRequiredService().GetDatabase(mongoContentDatabaseName))) + .As(); services.AddSingletonAs() .As(); @@ -74,8 +68,8 @@ namespace Squidex.Config.Domain services.AddTransientAs() .As(); - services.AddTransientAs(c => new DeleteContentCollections(mongoContentDatabase)) - .As(); + services.AddHealthChecks() + .AddCheck("MongoDB", tags: new[] { "node" }); services.AddSingletonAs() .AsOptional(); @@ -100,7 +94,10 @@ namespace Squidex.Config.Domain .AsOptional() .AsOptional>(); - services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetRequiredService(), c.GetRequiredService())) + services.AddSingletonAs(c => new MongoContentRepository( + c.GetRequiredService().GetDatabase(mongoContentDatabaseName), + c.GetRequiredService(), + c.GetRequiredService())) .AsOptional() .AsOptional>() .AsOptional(); diff --git a/src/Squidex/Config/Domain/SubscriptionServices.cs b/src/Squidex/Config/Domain/SubscriptionServices.cs index 2d2a0e706..f5f8ad07b 100644 --- a/src/Squidex/Config/Domain/SubscriptionServices.cs +++ b/src/Squidex/Config/Domain/SubscriptionServices.cs @@ -12,7 +12,6 @@ using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; using Squidex.Domain.Users; using Squidex.Infrastructure; -using Squidex.Infrastructure.DependencyInjection; namespace Squidex.Config.Domain { diff --git a/src/Squidex/Config/Orleans/OrleansServices.cs b/src/Squidex/Config/Orleans/OrleansServices.cs index 957a3cda5..aed630ded 100644 --- a/src/Squidex/Config/Orleans/OrleansServices.cs +++ b/src/Squidex/Config/Orleans/OrleansServices.cs @@ -15,7 +15,6 @@ using Orleans.Hosting; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; -using Squidex.Infrastructure.Configuration; using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.Orleans; diff --git a/src/Squidex/Config/Web/WebServices.cs b/src/Squidex/Config/Web/WebServices.cs index 3bafa44be..b94410f97 100644 --- a/src/Squidex/Config/Web/WebServices.cs +++ b/src/Squidex/Config/Web/WebServices.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Squidex.Config.Domain; -using Squidex.Infrastructure.DependencyInjection; using Squidex.Pipeline; using Squidex.Pipeline.Plugins; using Squidex.Pipeline.Robots; diff --git a/src/Squidex/Pipeline/Squid/SquidMiddleware.cs b/src/Squidex/Pipeline/Squid/SquidMiddleware.cs index 157c3c381..8bf5d0fb7 100644 --- a/src/Squidex/Pipeline/Squid/SquidMiddleware.cs +++ b/src/Squidex/Pipeline/Squid/SquidMiddleware.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -128,7 +127,7 @@ namespace Squidex.Pipeline.Squid private static string LoadSvg(string name) { - var assembly = typeof(SquidMiddleware).GetTypeInfo().Assembly; + var assembly = typeof(SquidMiddleware).Assembly; using (var resourceStream = assembly.GetManifestResourceStream($"Squidex.Pipeline.Squid.icon-{name}.svg")) { diff --git a/src/Squidex/Pipeline/Swagger/NSwagHelper.cs b/src/Squidex/Pipeline/Swagger/NSwagHelper.cs index 6cdc3be8d..ccc236f1f 100644 --- a/src/Squidex/Pipeline/Swagger/NSwagHelper.cs +++ b/src/Squidex/Pipeline/Swagger/NSwagHelper.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using NJsonSchema; @@ -23,7 +22,7 @@ namespace Squidex.Pipeline.Swagger { public static string LoadDocs(string name) { - var assembly = typeof(NSwagHelper).GetTypeInfo().Assembly; + var assembly = typeof(NSwagHelper).Assembly; using (var resourceStream = assembly.GetManifestResourceStream($"Squidex.Docs.{name}.md")) { diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 0c29f400d..a23db7df9 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -26,7 +26,6 @@ using Squidex.Config.Startup; using Squidex.Config.Web; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents; -using Squidex.Extensions.Actions.Twitter; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.Translations; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs rename to tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs index 3cef0cc4d..d7f647d61 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistry.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleElementRegistryTests.cs @@ -15,8 +15,10 @@ using Xunit; namespace Squidex.Domain.Apps.Core.Operations.HandleRules { - public class RuleElementRegistry + public class RuleElementRegistryTests { + private readonly RuleRegistry sut = new RuleRegistry(); + private abstract class MyRuleActionHandler : RuleActionHandler { protected MyRuleActionHandler(RuleEventFormatter formatter) @@ -25,7 +27,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules } } - [RuleActionHandler(typeof(MyRuleActionHandler))] [RuleAction( IconImage = "", IconColor = "#1e5470", @@ -52,12 +53,16 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [DataType(DataType.Password)] public string Password { get; set; } + [DataType(DataType.Custom)] public bool Boolean { get; set; } + [DataType(DataType.Custom)] public bool? BooleanOptional { get; set; } + [DataType(DataType.Custom)] public int Number { get; set; } + [DataType(DataType.Custom)] public int? NumberOptional { get; set; } } @@ -156,9 +161,9 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules IsRequired = false }); - RuleActionRegistry.Add(); + sut.Add(); - var currentDefinition = RuleActionRegistry.Actions.Values.First(); + var currentDefinition = sut.Actions.Values.First(); currentDefinition.Should().BeEquivalentTo(expected); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs index 6723ddeb2..64ee059ea 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs @@ -31,8 +31,8 @@ namespace Squidex.Domain.Apps.Core { var typeNameRegistry = new TypeNameRegistry() - .MapFields() - .MapRules() + .Map(new FieldRegistry()) + .Map(new RuleRegistry()) .MapUnmapped(typeof(TestUtils).Assembly); var serializerSettings = new JsonSerializerSettings @@ -123,7 +123,7 @@ namespace Squidex.Domain.Apps.Core foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) { var value = - property.PropertyType.GetTypeInfo().IsValueType ? + property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; @@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Core foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) { var value = - property.PropertyType.GetTypeInfo().IsValueType ? + property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null; diff --git a/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs b/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs index db90bc8d2..1b0ff3bfb 100644 --- a/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs +++ b/tests/Squidex.Infrastructure.Tests/TypeNameRegistryTests.cs @@ -6,7 +6,8 @@ // ========================================================================== using System; -using System.Reflection; +using System.Linq; +using FakeItEasy; using Squidex.Infrastructure.EventSourcing; using Xunit; @@ -26,6 +27,28 @@ namespace Squidex.Infrastructure { } + [Fact] + public void Should_call_provider_from_constructor() + { + var provider = A.Fake(); + + var registry = new TypeNameRegistry(Enumerable.Repeat(provider, 1)); + + A.CallTo(() => provider.Map(registry)) + .MustHaveHappened(); + } + + [Fact] + public void Should_call_provider() + { + var provider = A.Fake(); + + sut.Map(provider); + + A.CallTo(() => provider.Map(sut)) + .MustHaveHappened(); + } + [Fact] public void Should_register_and_retrieve_types() { @@ -50,10 +73,22 @@ namespace Squidex.Infrastructure Assert.Equal(typeof(MyType), sut.GetType("My")); } + [Fact] + public void Should_register_with_provider_from_assembly() + { + sut.Map(new AutoAssembyTypeProvider()); + + Assert.Equal("my", sut.GetName()); + Assert.Equal("my", sut.GetName(typeof(MyType))); + + Assert.Equal(typeof(MyType), sut.GetType("my")); + Assert.Equal(typeof(MyType), sut.GetType("My")); + } + [Fact] public void Should_register_from_assembly() { - sut.MapUnmapped(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly); + sut.MapUnmapped(typeof(TypeNameRegistryTests).Assembly); Assert.Equal("my", sut.GetName()); Assert.Equal("my", sut.GetName(typeof(MyType))); @@ -65,7 +100,7 @@ namespace Squidex.Infrastructure [Fact] public void Should_register_event_type_from_assembly() { - sut.MapUnmapped(typeof(TypeNameRegistryTests).GetTypeInfo().Assembly); + sut.MapUnmapped(typeof(TypeNameRegistryTests).Assembly); Assert.Equal("MyAddedEventV2", sut.GetName()); Assert.Equal("MyAddedEventV2", sut.GetName(typeof(MyAdded))); diff --git a/tools/Migrate_01/SquidexMigrations.cs b/tools/Migrate_01/SquidexMigrations.cs index 085bcf9dd..87fe65348 100644 --- a/tools/Migrate_01/SquidexMigrations.cs +++ b/tools/Migrate_01/SquidexMigrations.cs @@ -9,7 +9,7 @@ using System.Reflection; namespace Migrate_01 { - public static class SquidexMigrations + public sealed class SquidexMigrations { public static readonly Assembly Assembly = typeof(SquidexMigrations).Assembly; } From a5d170283d3ef37282dcaa0a3683f87d7740f817 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Thu, 21 Feb 2019 21:11:24 +0100 Subject: [PATCH 07/55] Lazy fixes. --- .../ValidateContent/JsonValueConverter.cs | 2 +- .../Apps/Indexes/AppsByNameIndexGrain.cs | 16 ++++-- .../Contents/ContentSchedulerGrain.cs | 18 ++++--- .../MongoDb/BsonJsonConvention.cs | 52 +++++++++++-------- .../MongoDb/InstantSerializer.cs | 16 +++--- .../MongoDb/MongoRepositoryBase.cs | 1 + .../MongoDb/RefTokenSerializer.cs | 16 +++--- .../RedisPubSub.cs | 19 ++++--- .../ImageSharpAssetThumbnailGenerator.cs | 4 +- .../DependencyInjectionExtensions.cs | 9 ---- src/Squidex.Infrastructure/Lazier.cs | 20 ------- src/Squidex.Infrastructure/Singletons.cs | 5 -- .../Translations/DeepLTranslator.cs | 2 +- .../Config/Domain/InfrastructureServices.cs | 12 ++--- src/Squidex/Config/Domain/LoggingServices.cs | 10 ++-- src/Squidex/Config/Domain/StoreServices.cs | 2 +- .../Pipeline/Plugins/PluginExtensions.cs | 2 - 17 files changed, 90 insertions(+), 116 deletions(-) delete mode 100644 src/Squidex.Infrastructure/Lazier.cs diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index 2972cd648..fff4b75e7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -179,7 +179,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent foreach (var item in array) { - if (item is JsonNull n) + if (item is JsonNull) { result.Add(null); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs index ffd7d333a..29dab6b57 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByNameIndexGrain.cs @@ -40,11 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes public Task ReserveAppAsync(Guid appId, string name) { - var canReserve = - !State.Apps.ContainsKey(name) && - !State.Apps.Any(x => x.Value == appId) && - !reservedIds.Contains(appId) && - !reservedNames.Contains(name); + var canReserve = !IsInUse(appId, name) && !IsReserved(appId, name); if (canReserve) { @@ -55,6 +51,16 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes return Task.FromResult(canReserve); } + private bool IsInUse(Guid appId, string name) + { + return State.Apps.ContainsKey(name) || State.Apps.All(x => x.Value == appId); + } + + private bool IsReserved(Guid appId, string name) + { + return reservedIds.Contains(appId) || reservedNames.Contains(name); + } + public Task RemoveReservationAsync(Guid appId, string name) { reservedIds.Remove(appId); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs index d1885adcd..051f66a7a 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentSchedulerGrain.cs @@ -22,15 +22,15 @@ namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentSchedulerGrain : Grain, IContentSchedulerGrain, IRemindable { - private readonly Lazy contentRepository; - private readonly Lazy commandBus; + private readonly IContentRepository contentRepository; + private readonly ICommandBus commandBus; private readonly IClock clock; private readonly ISemanticLog log; private TaskScheduler scheduler; public ContentSchedulerGrain( - Lazy contentRepository, - Lazy commandBus, + IContentRepository contentRepository, + ICommandBus commandBus, IClock clock, ISemanticLog log) { @@ -39,9 +39,11 @@ namespace Squidex.Domain.Apps.Entities.Contents Guard.NotNull(clock, nameof(clock)); Guard.NotNull(log, nameof(log)); - this.contentRepository = contentRepository; - this.commandBus = commandBus; this.clock = clock; + + this.commandBus = commandBus; + this.contentRepository = contentRepository; + this.log = log; } @@ -66,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var now = clock.GetCurrentInstant(); - return contentRepository.Value.QueryScheduledWithoutDataAsync(now, content => + return contentRepository.QueryScheduledWithoutDataAsync(now, content => { return Dispatch(async () => { @@ -78,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var command = new ChangeContentStatus { ContentId = content.Id, Status = job.Status, Actor = job.ScheduledBy, JobId = job.Id }; - await commandBus.Value.PublishAsync(command); + await commandBus.PublishAsync(command); } } catch (Exception ex) diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs index 77019a716..7fe870e3c 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs @@ -8,6 +8,7 @@ using System; using System.Linq; using System.Reflection; +using System.Threading; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using Newtonsoft.Json; @@ -17,36 +18,41 @@ namespace Squidex.Infrastructure.MongoDb { public static class BsonJsonConvention { + private static volatile int isRegistered; + public static void Register(JsonSerializer serializer) { - var pack = new ConventionPack(); - - pack.AddMemberMapConvention("JsonBson", memberMap => + if (Interlocked.Increment(ref isRegistered) == 1) { - var attributes = memberMap.MemberInfo.GetCustomAttributes(); + var pack = new ConventionPack(); - if (attributes.OfType().Any()) + pack.AddMemberMapConvention("JsonBson", memberMap => { - var bsonSerializerType = typeof(BsonJsonSerializer<>).MakeGenericType(memberMap.MemberType); - var bsonSerializer = Activator.CreateInstance(bsonSerializerType, serializer); + var attributes = memberMap.MemberInfo.GetCustomAttributes(); - memberMap.SetSerializer((IBsonSerializer)bsonSerializer); - } - else if (memberMap.MemberType == typeof(JToken)) - { - memberMap.SetSerializer(JTokenSerializer.Instance); - } - else if (memberMap.MemberType == typeof(JObject)) - { - memberMap.SetSerializer(JTokenSerializer.Instance); - } - else if (memberMap.MemberType == typeof(JValue)) - { - memberMap.SetSerializer(JTokenSerializer.Instance); - } - }); + if (attributes.OfType().Any()) + { + var bsonSerializerType = typeof(BsonJsonSerializer<>).MakeGenericType(memberMap.MemberType); + var bsonSerializer = Activator.CreateInstance(bsonSerializerType, serializer); + + memberMap.SetSerializer((IBsonSerializer)bsonSerializer); + } + else if (memberMap.MemberType == typeof(JToken)) + { + memberMap.SetSerializer(JTokenSerializer.Instance); + } + else if (memberMap.MemberType == typeof(JObject)) + { + memberMap.SetSerializer(JTokenSerializer.Instance); + } + else if (memberMap.MemberType == typeof(JValue)) + { + memberMap.SetSerializer(JTokenSerializer.Instance); + } + }); - ConventionRegistry.Register("json", pack, t => true); + ConventionRegistry.Register("json", pack, t => true); + } } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs index b4a5d5926..6979cafe3 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/InstantSerializer.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; +using System.Threading; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using NodaTime; @@ -14,16 +14,14 @@ namespace Squidex.Infrastructure.MongoDb { public sealed class InstantSerializer : SerializerBase, IBsonPolymorphicSerializer { - private static readonly Lazy Registerer = new Lazy(() => - { - BsonSerializer.RegisterSerializer(new InstantSerializer()); - - return true; - }); + private static volatile int isRegistered; - public static bool Register() + public static void Register() { - return !Registerer.IsValueCreated && Registerer.Value; + if (Interlocked.Increment(ref isRegistered) == 1) + { + BsonSerializer.RegisterSerializer(new InstantSerializer()); + } } public bool IsDiscriminatorCompatibleWithObjectSerializer diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs index 00237ee83..689bdfe8e 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs @@ -44,6 +44,7 @@ namespace Squidex.Infrastructure.MongoDb static MongoRepositoryBase() { RefTokenSerializer.Register(); + InstantSerializer.Register(); } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs index 6f8761148..b4c45f945 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/RefTokenSerializer.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; +using System.Threading; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; @@ -13,16 +13,14 @@ namespace Squidex.Infrastructure.MongoDb { public class RefTokenSerializer : ClassSerializerBase { - private static readonly Lazy Registerer = new Lazy(() => - { - BsonSerializer.RegisterSerializer(new RefTokenSerializer()); - - return true; - }); + private static volatile int isRegistered; - public static bool Register() + public static void Register() { - return !Registerer.IsValueCreated && Registerer.Value; + if (Interlocked.Increment(ref isRegistered) == 1) + { + BsonSerializer.RegisterSerializer(new RefTokenSerializer()); + } } protected override RefToken DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) diff --git a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs index 3feaf1510..74ef5c100 100644 --- a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs +++ b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs @@ -19,22 +19,19 @@ namespace Squidex.Infrastructure public sealed class RedisPubSub : IPubSub, IInitializable { private readonly ConcurrentDictionary subscriptions = new ConcurrentDictionary(); - private readonly Lazy redisClient; + private readonly IConnectionMultiplexer redis; private readonly IJsonSerializer serializer; - private readonly Lazy redisSubscriber; private readonly ISemanticLog log; + private ISubscriber redisSubscriber; - public RedisPubSub(Lazy redis, IJsonSerializer serializer, ISemanticLog log) + public RedisPubSub(IConnectionMultiplexer redis, IJsonSerializer serializer, ISemanticLog log) { Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(redis, nameof(redis)); Guard.NotNull(log, nameof(log)); this.log = log; - - redisClient = redis; - redisSubscriber = new Lazy(() => redis.Value.GetSubscriber()); - + this.redis = redis; this.serializer = serializer; } @@ -42,13 +39,15 @@ namespace Squidex.Infrastructure { try { - redisClient.Value.GetStatus(); + redisSubscriber = redis.GetSubscriber(); + + redis.GetStatus(); return TaskHelper.Done; } catch (Exception ex) { - throw new ConfigurationException($"Redis connection failed to connect to database {redisClient.Value.Configuration}", ex); + throw new ConfigurationException($"Redis connection failed to connect to database {redis.Configuration}", ex); } } @@ -66,7 +65,7 @@ namespace Squidex.Infrastructure { var typeName = typeof(T).FullName; - return (RedisSubscription)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription(c.redisSubscriber.Value, serializer, k, c.log)); + return (RedisSubscription)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription(c.redisSubscriber, serializer, k, c.log)); } } } diff --git a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs index 82c4e2dc9..f36089eeb 100644 --- a/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs +++ b/src/Squidex.Infrastructure/Assets/ImageSharp/ImageSharpAssetThumbnailGenerator.cs @@ -19,8 +19,8 @@ namespace Squidex.Infrastructure.Assets.ImageSharp { public ImageSharpAssetThumbnailGenerator() { - SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Jpeg); - SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Png); + Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Jpeg); + Configuration.Default.ImageFormatsManager.AddImageFormat(ImageFormats.Png); } public Task CreateThumbnailAsync(Stream source, Stream destination, int? width, int? height, string mode) diff --git a/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs index da40d75eb..13f245759 100644 --- a/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs +++ b/src/Squidex.Infrastructure/DependencyInjection/DependencyInjectionExtensions.cs @@ -72,15 +72,6 @@ namespace Microsoft.Extensions.DependencyInjection return new InterfaceRegistrator(services); } - public static InterfaceRegistrator AddSingletonAs(this IServiceCollection services, T instance) where T : class - { - services.AddSingleton(typeof(T), instance); - - RegisterDefaults(services); - - return new InterfaceRegistrator(services); - } - public static InterfaceRegistrator AddSingletonAs(this IServiceCollection services) where T : class { services.AddSingleton(); diff --git a/src/Squidex.Infrastructure/Lazier.cs b/src/Squidex.Infrastructure/Lazier.cs deleted file mode 100644 index aae97240c..000000000 --- a/src/Squidex.Infrastructure/Lazier.cs +++ /dev/null @@ -1,20 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Microsoft.Extensions.DependencyInjection; - -namespace Squidex.Infrastructure -{ - public sealed class Lazier : Lazy where T : class - { - public Lazier(IServiceProvider provider) - : base(provider.GetRequiredService) - { - } - } -} diff --git a/src/Squidex.Infrastructure/Singletons.cs b/src/Squidex.Infrastructure/Singletons.cs index 494157155..e0eb715ab 100644 --- a/src/Squidex.Infrastructure/Singletons.cs +++ b/src/Squidex.Infrastructure/Singletons.cs @@ -18,10 +18,5 @@ namespace Squidex.Infrastructure { return Instances.GetOrAdd(key, factory); } - - public static Lazy GetOrAddLazy(string key, Func factory) - { - return new Lazy(() => Instances.GetOrAdd(key, factory)); - } } } diff --git a/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs b/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs index 09920e6b6..5409faca1 100644 --- a/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs +++ b/src/Squidex.Infrastructure/Translations/DeepLTranslator.cs @@ -87,7 +87,7 @@ namespace Squidex.Infrastructure.Translations return new Translation(TranslationResult.Failed, resultText: responseString); } - private string GetLanguageCode(Language language) + private static string GetLanguageCode(Language language) { return language.Iso2Code.Substring(0, 2).ToUpperInvariant(); } diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index 8e1b2e042..7b2e5d335 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -36,7 +36,12 @@ namespace Squidex.Config.Domain .AddCheck("Orleans", tags: new[] { "cluster" }) .AddCheck("Orleans App", tags: new[] { "cluster" }); - services.AddSingletonAs(SystemClock.Instance) + services.AddSingletonAs(c => new CachingUsageTracker( + c.GetRequiredService(), + c.GetRequiredService())) + .As(); + + services.AddSingletonAs(_ => SystemClock.Instance) .As(); services.AddSingletonAs() @@ -48,9 +53,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs(c => new CachingUsageTracker(c.GetRequiredService(), c.GetRequiredService())) - .As(); - services.AddSingletonAs() .As(); @@ -68,8 +70,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - - services.AddTransient(typeof(Lazy<>), typeof(Lazier<>)); } } } diff --git a/src/Squidex/Config/Domain/LoggingServices.cs b/src/Squidex/Config/Domain/LoggingServices.cs index 59e7932e3..60c4d6333 100644 --- a/src/Squidex/Config/Domain/LoggingServices.cs +++ b/src/Squidex/Config/Domain/LoggingServices.cs @@ -20,12 +20,12 @@ namespace Squidex.Config.Domain { if (config.GetValue("logging:human")) { - services.AddSingletonAs(JsonLogWriterFactory.Readable()) + services.AddSingletonAs(_ => JsonLogWriterFactory.Readable()) .As(); } else { - services.AddSingletonAs(JsonLogWriterFactory.Default()) + services.AddSingletonAs(_ => JsonLogWriterFactory.Default()) .As(); } @@ -33,16 +33,16 @@ namespace Squidex.Config.Domain if (!string.IsNullOrWhiteSpace(loggingFile)) { - services.AddSingletonAs(new FileChannel(loggingFile)) + services.AddSingletonAs(_ => new FileChannel(loggingFile)) .As(); } var useColors = config.GetValue("logging:colors"); - services.AddSingletonAs(new ConsoleLogChannel(useColors)) + services.AddSingletonAs(_ => new ConsoleLogChannel(useColors)) .As(); - services.AddSingletonAs(c => new ApplicationInfoLogAppender(typeof(Program).Assembly, Guid.NewGuid())) + services.AddSingletonAs(_ => new ApplicationInfoLogAppender(typeof(Program).Assembly, Guid.NewGuid())) .As(); services.AddSingletonAs() diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index a980bafc6..63559f3d4 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -50,7 +50,7 @@ namespace Squidex.Config.Domain services.AddSingleton(typeof(ISnapshotStore<,>), typeof(MongoSnapshotStore<,>)); - services.AddSingletonAs(c => Singletons.GetOrAdd(mongoConfiguration, s => new MongoClient(s))) + services.AddSingletonAs(_ => Singletons.GetOrAdd(mongoConfiguration, s => new MongoClient(s))) .As(); services.AddSingletonAs(c => c.GetRequiredService().GetDatabase(mongoDatabaseName)) diff --git a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs index 507d285af..0ba06a608 100644 --- a/src/Squidex/Pipeline/Plugins/PluginExtensions.cs +++ b/src/Squidex/Pipeline/Plugins/PluginExtensions.cs @@ -83,8 +83,6 @@ namespace Squidex.Pipeline.Plugins private static PluginLoader LoadPlugin(string pluginPath) { - var fullPath = GetPaths(pluginPath); - foreach (var candidate in GetPaths(pluginPath)) { if (candidate.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)) From e037196e855eadfbf5f4f867c8e9f87f3f6ab04d Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 22 Feb 2019 20:23:05 +0100 Subject: [PATCH 08/55] Example plugin with in-memory asset store. --- .../Samples/MemoryAssetStorePlugin.cs | 29 +++++ .../Assets/MemoryAssetStore.cs | 119 ++++++++++++++++++ .../Assets/NoopAssetStore.cs | 52 ++++++++ .../Configuration/ConfigurationExtensions.cs | 4 + src/Squidex/Config/Domain/AssetServices.cs | 5 + .../Assets/MemoryAssetStoreTests.cs | 31 +++++ .../Assets/MongoGridFsAssetStoreTests.cs | 4 +- 7 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 extensions/Squidex.Extensions/Samples/MemoryAssetStorePlugin.cs create mode 100644 src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs create mode 100644 src/Squidex.Infrastructure/Assets/NoopAssetStore.cs create mode 100644 tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs diff --git a/extensions/Squidex.Extensions/Samples/MemoryAssetStorePlugin.cs b/extensions/Squidex.Extensions/Samples/MemoryAssetStorePlugin.cs new file mode 100644 index 000000000..b4a018ed5 --- /dev/null +++ b/extensions/Squidex.Extensions/Samples/MemoryAssetStorePlugin.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Squidex.Infrastructure.Assets; +using Squidex.Infrastructure.Plugins; + +namespace Squidex.Extensions.Samples +{ + public sealed class MemoryAssetStorePlugin : IPlugin + { + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + var storeType = configuration.GetValue("assetStore:type"); + + if (string.Equals(storeType, "Memory", StringComparison.OrdinalIgnoreCase)) + { + services.AddSingletonAs() + .As(); + } + } + } +} diff --git a/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs b/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs new file mode 100644 index 000000000..7c00f9a85 --- /dev/null +++ b/src/Squidex.Infrastructure/Assets/MemoryAssetStore.cs @@ -0,0 +1,119 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Concurrent; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Infrastructure.Assets +{ + public sealed class MemoryAssetStore : IAssetStore + { + private readonly ConcurrentDictionary streams = new ConcurrentDictionary(); + private readonly AsyncLock readerLock = new AsyncLock(); + private readonly AsyncLock writerLock = new AsyncLock(); + + public string GeneratePublicUrl(string id, long version, string suffix) + { + return null; + } + + public async Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + { + Guard.NotNullOrEmpty(sourceFileName, nameof(sourceFileName)); + Guard.NotNullOrEmpty(id, nameof(id)); + + if (!streams.TryGetValue(sourceFileName, out var sourceStream)) + { + throw new AssetNotFoundException(sourceFileName); + } + + using (await readerLock.LockAsync()) + { + await UploadAsync(id, version, suffix, sourceStream, ct); + } + } + + public async Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + Guard.NotNullOrEmpty(id, nameof(id)); + + var fileName = GetFileName(id, version, suffix); + + if (!streams.TryGetValue(fileName, out var sourceStream)) + { + throw new AssetNotFoundException(fileName); + } + + using (await readerLock.LockAsync()) + { + try + { + await sourceStream.CopyToAsync(stream, 81920, ct); + } + finally + { + sourceStream.Position = 0; + } + } + } + + public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + Guard.NotNullOrEmpty(id, nameof(id)); + + return UploadAsync(GetFileName(id, version, suffix), stream, ct); + } + + public async Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) + { + var memoryStream = new MemoryStream(); + + if (streams.TryAdd(fileName, memoryStream)) + { + using (await writerLock.LockAsync()) + { + try + { + await stream.CopyToAsync(memoryStream, 81920, ct); + } + finally + { + memoryStream.Position = 0; + } + } + } + else + { + throw new AssetAlreadyExistsException(fileName); + } + } + + public Task DeleteAsync(string id, long version, string suffix) + { + Guard.NotNullOrEmpty(id, nameof(id)); + + return DeleteAsync(GetFileName(id, version, suffix)); + } + + public Task DeleteAsync(string fileName) + { + Guard.NotNullOrEmpty(fileName, nameof(fileName)); + + streams.TryRemove(fileName, out _); + + return TaskHelper.Done; + } + + private string GetFileName(string id, long version, string suffix) + { + return StringExtensions.JoinNonEmpty("_", id, version.ToString(), suffix); + } + } +} diff --git a/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs b/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs new file mode 100644 index 000000000..5c98cd77a --- /dev/null +++ b/src/Squidex.Infrastructure/Assets/NoopAssetStore.cs @@ -0,0 +1,52 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Assets +{ + public sealed class NoopAssetStore : IAssetStore + { + public string GeneratePublicUrl(string id, long version, string suffix) + { + return null; + } + + public Task CopyAsync(string sourceFileName, string id, long version, string suffix, CancellationToken ct = default) + { + throw new NotSupportedException(); + } + + public Task DownloadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + throw new NotSupportedException(); + } + + public Task UploadAsync(string fileName, Stream stream, CancellationToken ct = default) + { + throw new NotSupportedException(); + } + + public Task UploadAsync(string id, long version, string suffix, Stream stream, CancellationToken ct = default) + { + throw new NotSupportedException(); + } + + public Task DeleteAsync(string fileName) + { + throw new NotSupportedException(); + } + + public Task DeleteAsync(string id, long version, string suffix) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs index 62d0521a4..fafb0ee84 100644 --- a/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs +++ b/src/Squidex.Infrastructure/Configuration/ConfigurationExtensions.cs @@ -54,6 +54,10 @@ namespace Microsoft.Extensions.Configuration { action(); } + else if (options.TryGetValue("default", out action)) + { + action(); + } else { throw new ConfigurationException($"Unsupported value '{value}' for '{path}', supported: {string.Join(" ", options.Keys)}."); diff --git a/src/Squidex/Config/Domain/AssetServices.cs b/src/Squidex/Config/Domain/AssetServices.cs index 617c8ff8c..5b8e36706 100644 --- a/src/Squidex/Config/Domain/AssetServices.cs +++ b/src/Squidex/Config/Domain/AssetServices.cs @@ -22,6 +22,11 @@ namespace Squidex.Config.Domain { config.ConfigureByOption("assetStore:type", new Options { + ["Default"] = () => + { + services.AddSingletonAs() + .AsOptional(); + }, ["Folder"] = () => { var path = config.GetRequiredValue("assetStore:folder:path"); diff --git a/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs new file mode 100644 index 000000000..f11cb347d --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Assets/MemoryAssetStoreTests.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Xunit; + +namespace Squidex.Infrastructure.Assets +{ + public class MemoryAssetStoreTests : AssetStoreTests + { + public override MemoryAssetStore CreateStore() + { + return new MemoryAssetStore(); + } + + public override void Dispose() + { + } + + [Fact] + public void Should_not_calculate_source_url() + { + var url = Sut.GeneratePublicUrl(AssetId, 1, null); + + Assert.Null(url); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs index 4fb5a6a66..e80d01ca9 100644 --- a/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Assets/MongoGridFsAssetStoreTests.cs @@ -13,11 +13,11 @@ using Xunit; namespace Squidex.Infrastructure.Assets { - internal class MongoGridFSAssetStoreTests : AssetStoreTests + internal class MongoGridFsAssetStoreTests : AssetStoreTests { private static readonly IGridFSBucket GridFSBucket; - static MongoGridFSAssetStoreTests() + static MongoGridFsAssetStoreTests() { var mongoClient = new MongoClient("mongodb://localhost"); var mongoDatabase = mongoClient.GetDatabase("Test"); From a1c7a4287f4546f2c6fb9f2cb73a55edb485175a Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 22 Feb 2019 20:52:04 +0100 Subject: [PATCH 09/55] Metadata fixed and improvements in trigger UI. --- .../Actions/AzureQueue/AzureQueueAction.cs | 2 +- .../Actions/Discourse/DiscourseAction.cs | 4 +- .../Discourse/DiscourseActionHandler.cs | 3 +- .../ElasticSearch/ElasticSearchAction.cs | 2 +- .../ElasticSearchActionHandler.cs | 4 +- .../Actions/Email/EmailAction.cs | 27 +++--- .../Actions/Medium/MediumAction.cs | 9 +- .../Actions/Twitter/TweetAction.cs | 1 + .../Actions/Webhook/WebhookAction.cs | 2 - .../actions/generic-action.component.html | 4 +- .../actions/generic-action.component.scss | 6 +- .../pages/rules/rules-page.component.html | 96 +++++++++---------- .../pages/clients/clients-page.component.html | 2 +- .../patterns/patterns-page.component.html | 7 +- src/Squidex/app/theme/_forms.scss | 2 + 15 files changed, 91 insertions(+), 80 deletions(-) diff --git a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs index 41c5be7a9..002da0d83 100644 --- a/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs +++ b/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueAction.cs @@ -23,7 +23,7 @@ namespace Squidex.Extensions.Actions.AzureQueue public sealed class AzureQueueAction : RuleAction { [Required] - [Display(Name = "Connection String", Description = "The connection string to the storage account.")] + [Display(Name = "Connection", Description = "The connection string to the storage account.")] [DataType(DataType.Text)] [Formattable] public string ConnectionString { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs index 295c7f683..8e1c04011 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseAction.cs @@ -23,7 +23,7 @@ namespace Squidex.Extensions.Actions.Discourse { [AbsoluteUrl] [Required] - [Display(Name = "Url", Description = "The url to the discourse server.")] + [Display(Name = "Server Url", Description = "The url to the discourse server.")] [DataType(DataType.Url)] public Uri Url { get; set; } @@ -33,7 +33,7 @@ namespace Squidex.Extensions.Actions.Discourse public string ApiKey { get; set; } [Required] - [Display(Name = "Api Username", Description = "The api username to authenticate to your discourse server.")] + [Display(Name = "Api User", Description = "The api username to authenticate to your discourse server.")] [DataType(DataType.Text)] public string ApiUsername { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs index b0811ba17..d408569df 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs @@ -34,7 +34,6 @@ namespace Squidex.Extensions.Actions.Discourse var json = new Dictionary { - ["raw"] = Format(action.Text, @event), ["title"] = Format(action.Title, @event) }; @@ -56,6 +55,8 @@ namespace Squidex.Extensions.Actions.Discourse RequestBody = requestBody }; + json["raw"] = Format(action.Text, @event); + var description = action.Topic.HasValue ? DescriptionCreateTopic : diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs index 298e4ad66..826f86f6d 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchAction.cs @@ -23,7 +23,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch { [AbsoluteUrl] [Required] - [Display(Name = "Host", Description = "The hostname of the elastic search instance or cluster.")] + [Display(Name = "Server Url", Description = "The url to the elastic search instance or cluster.")] [DataType(DataType.Url)] public Uri Host { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs index 991725964..10c3b0a79 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs @@ -45,9 +45,9 @@ namespace Squidex.Extensions.Actions.ElasticSearch var ruleJob = new ElasticSearchJob { Host = action.Host.ToString(), - ContentId = contentId, IndexName = Format(action.IndexName, @event), - IndexType = Format(action.IndexType, @event) + IndexType = Format(action.IndexType, @event), + ContentId = contentId }; if (contentEvent.Type == EnrichedContentEventType.Deleted || diff --git a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs index c801e629b..e6ab737b7 100644 --- a/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs +++ b/extensions/Squidex.Extensions/Actions/Email/EmailAction.cs @@ -20,50 +20,51 @@ namespace Squidex.Extensions.Actions.Email public sealed class EmailAction : RuleAction { [Required] - [Display(Name = "ServerHost", Description = "The IP address or host to the SMTP server.")] + [Display(Name = "Server Host", Description = "The IP address or host to the SMTP server.")] [DataType(DataType.Text)] public string ServerHost { get; set; } [Required] - [Display(Name = "ServerPort", Description = "The port to the SMTP server.")] + [Display(Name = "Server Port", Description = "The port to the SMTP server.")] [DataType(DataType.Custom)] public int ServerPort { get; set; } [Required] - [Display(Name = "ServerUseSsl", Description = "Specify whether the SMPT client uses Secure Sockets Layer (SSL) to encrypt the connection.")] + [Display(Name = "Use SSL", Description = "Specify whether the SMPT client uses Secure Sockets Layer (SSL) to encrypt the connection.")] [DataType(DataType.Custom)] public bool ServerUseSsl { get; set; } [Required] - [Display(Name = "ServerUsername", Description = "The username for the SMTP server.")] - [DataType(DataType.Text)] - public string ServerUsername { get; set; } - - [Required] - [Display(Name = "ServerPassword", Description = "The password for the SMTP server.")] + [Display(Name = "Password", Description = "The password for the SMTP server.")] [DataType(DataType.Password)] public string ServerPassword { get; set; } [Required] - [Display(Name = "MessageFrom", Description = "The email sending address.")] + [Display(Name = "Username", Description = "The username for the SMTP server.")] + [DataType(DataType.Text)] + [Formattable] + public string ServerUsername { get; set; } + + [Required] + [Display(Name = "From Address", Description = "The email sending address.")] [DataType(DataType.Text)] [Formattable] public string MessageFrom { get; set; } [Required] - [Display(Name = "MessageTo", Description = "The email message will be sent to.")] + [Display(Name = "To Address", Description = "The email message will be sent to.")] [DataType(DataType.Text)] [Formattable] public string MessageTo { get; set; } [Required] - [Display(Name = "MessageSubject", Description = "The subject line for this email message.")] + [Display(Name = "Subject", Description = "The subject line for this email message.")] [DataType(DataType.Text)] [Formattable] public string MessageSubject { get; set; } [Required] - [Display(Name = "MessageBody", Description = "The message body.")] + [Display(Name = "Body", Description = "The message body.")] [DataType(DataType.MultilineText)] [Formattable] public string MessageBody { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs index 3c6ce25e2..3781eb9de 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumAction.cs @@ -41,14 +41,15 @@ namespace Squidex.Extensions.Actions.Medium [Formattable] public string CanonicalUrl { get; set; } - [Display(Name = "PublicationId", Description = "Optional publication id.")] - [DataType(DataType.Text)] - public string PublicationId { get; set; } - [Display(Name = "Tags", Description = "The optional comma separated list of tags.")] [DataType(DataType.Text)] + [Formattable] public string Tags { get; set; } + [Display(Name = "Publication Id", Description = "Optional publication id.")] + [DataType(DataType.Text)] + public string PublicationId { get; set; } + [Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")] [DataType(DataType.Custom)] public bool IsHtml { get; set; } diff --git a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs index 5f68d9fd4..67bb45e63 100644 --- a/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs +++ b/extensions/Squidex.Extensions/Actions/Twitter/TweetAction.cs @@ -32,6 +32,7 @@ namespace Squidex.Extensions.Actions.Twitter [Required] [Display(Name = "Text", Description = "The text that is sent as tweet to twitter.")] [DataType(DataType.MultilineText)] + [Formattable] public string Text { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs index 2cf8e339a..4c129531e 100644 --- a/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs +++ b/extensions/Squidex.Extensions/Actions/Webhook/WebhookAction.cs @@ -9,7 +9,6 @@ using System; using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Infrastructure; namespace Squidex.Extensions.Actions.Webhook { @@ -21,7 +20,6 @@ namespace Squidex.Extensions.Actions.Webhook ReadMore = "https://en.wikipedia.org/wiki/Webhook")] public sealed class WebhookAction : RuleAction { - [AbsoluteUrl] [Required] [Display(Name = "Url", Description = "The url to the webhook.")] [DataType(DataType.Text)] diff --git a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html index 5c0438347..3ab92a516 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html +++ b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.html @@ -1,6 +1,8 @@
- +
diff --git a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss index 6f7a94e21..91941c2fc 100644 --- a/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss +++ b/src/Squidex/app/features/rules/pages/rules/actions/generic-action.component.scss @@ -2,5 +2,9 @@ @import '_mixins'; .form-check { - padding-top: .5rem; + margin-bottom: -.25rem; +} + +textarea { + height: 15rem; } \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index 313951c46..3161d203c 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -19,58 +19,56 @@ - - -
- No Rule created yet. + +
+ No rule created yet. - -
+ +
- - - - - - - - - - - - -
-

If

-
- - - - -

then

-
- - - - - - - -
+ + + + + + + + + + + + +
+

If

+
+ + + + +

then

+
+ + + + + + + +
- - - - + + +
diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html index b0fff5866..5ef4d921b 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html @@ -14,7 +14,7 @@
- +
No client created yet.
diff --git a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html index b82d2553d..9d1dc860e 100644 --- a/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html +++ b/src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html @@ -14,12 +14,15 @@
- +
No pattern created yet.
- + + +
diff --git a/src/Squidex/app/theme/_forms.scss b/src/Squidex/app/theme/_forms.scss index b1ee1d822..ba6d680c4 100644 --- a/src/Squidex/app/theme/_forms.scss +++ b/src/Squidex/app/theme/_forms.scss @@ -6,6 +6,7 @@ // .form-control { &.ng-invalid { + &.ng-touched, &.ng-dirty { & { border-color: $color-theme-error; @@ -199,6 +200,7 @@ input { } &.ng-invalid { + &.ng-touched, &.ng-dirty { & { @include box-shadow-none; From 7eb980fc870db4907387701e840338019349d9aa Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 22 Feb 2019 22:10:57 +0100 Subject: [PATCH 10/55] Async pipe improvements. --- .../pages/restore/restore-page.component.html | 64 +++++----- .../pages/users/user-page.component.html | 12 +- .../pages/users/users-page.component.html | 16 +-- .../apps/pages/apps-page.component.html | 8 +- .../pages/assets-filters-page.component.html | 5 +- .../pages/content/content-page.component.html | 1 + .../contents-filters-page.component.html | 1 + .../shared/assets-editor.component.html | 4 +- .../shared/content-status.component.html | 14 +-- .../shared/content-status.component.ts | 10 +- .../shared/contents-selector.component.html | 4 +- .../shared/due-time-selector.component.html | 50 ++++---- .../pages/rules/rule-element.component.html | 1 + .../pages/rules/rules-page.component.html | 5 +- .../pages/schemas/schema-form.component.html | 1 + .../pages/backups/backups-page.component.html | 116 +++++++++--------- .../pages/clients/client.component.html | 6 +- .../languages/languages-page.component.html | 14 +-- .../pages/plans/plans-page.component.html | 100 ++++++++------- .../pages/roles/roles-page.component.html | 4 +- .../angular/forms/autocomplete.component.html | 1 + .../angular/forms/stars.component.html | 12 +- .../components/markdown-editor.component.html | 4 +- .../components/rich-editor.component.html | 4 +- 24 files changed, 238 insertions(+), 219 deletions(-) diff --git a/src/Squidex/app/features/administration/pages/restore/restore-page.component.html b/src/Squidex/app/features/administration/pages/restore/restore-page.component.html index f6d4adb6c..9097e9524 100644 --- a/src/Squidex/app/features/administration/pages/restore/restore-page.component.html +++ b/src/Squidex/app/features/administration/pages/restore/restore-page.component.html @@ -6,48 +6,46 @@
- -
-
-
-
-
- -
-
- -
-
- -
+
+
+
+
+
+
- -
-

Last Restore Operation

+
+
- -
- {{job.url}} +
+
-
-
-
- {{row}} + +
+

Last Restore Operation

+
+ +
+ {{job.url}}
-
diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index 0c19b2998..3f8918635 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -16,13 +16,11 @@ - - - - - + + + diff --git a/src/Squidex/app/features/administration/pages/users/users-page.component.html b/src/Squidex/app/features/administration/pages/users/users-page.component.html index dde3ab83c..a65cdab0e 100644 --- a/src/Squidex/app/features/administration/pages/users/users-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/users-page.component.html @@ -47,8 +47,8 @@
- - +
+ diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.html b/src/Squidex/app/features/apps/pages/apps-page.component.html index 6bfb9fa97..3a9d658e9 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.html +++ b/src/Squidex/app/features/apps/pages/apps-page.component.html @@ -100,9 +100,13 @@ - + + - + + \ No newline at end of file diff --git a/src/Squidex/app/features/assets/pages/assets-filters-page.component.html b/src/Squidex/app/features/assets/pages/assets-filters-page.component.html index 0a32d5244..f6c48e822 100644 --- a/src/Squidex/app/features/assets/pages/assets-filters-page.component.html +++ b/src/Squidex/app/features/assets/pages/assets-filters-page.component.html @@ -31,7 +31,10 @@

Saved queries

+ [class.active]="isSelectedQuery(query.filter)"> + + {{query.name}} + diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.html b/src/Squidex/app/features/content/pages/content/content-page.component.html index fd527a854..d27447adc 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.html +++ b/src/Squidex/app/features/content/pages/content/content-page.component.html @@ -30,6 +30,7 @@ + - + + \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/content-status.component.html b/src/Squidex/app/features/content/shared/content-status.component.html index 22de0d0b8..b03f43a5b 100644 --- a/src/Squidex/app/features/content/shared/content-status.component.html +++ b/src/Squidex/app/features/content/shared/content-status.component.html @@ -1,13 +1,13 @@ - - - + + + - - - + + + - +{{displayStatus}} \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/content-status.component.ts b/src/Squidex/app/features/content/shared/content-status.component.ts index 527002671..85011a079 100644 --- a/src/Squidex/app/features/content/shared/content-status.component.ts +++ b/src/Squidex/app/features/content/shared/content-status.component.ts @@ -34,10 +34,16 @@ export class ContentStatusComponent { @Input() public alignMiddle = true; - public get displayStatus() { + public get tooltipText() { if (this.scheduledAt) { return `Will be set to '${this.scheduledTo}' at ${this.scheduledAt.toStringFormat('LLLL')}`; - } else if (this.isPending) { + } else { + return this.status; + } + } + + public get displayStatus() { + if (this.isPending) { return 'Pending'; } else { return this.status; diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.html b/src/Squidex/app/features/content/shared/contents-selector.component.html index a174dd63f..6b57c596f 100644 --- a/src/Squidex/app/features/content/shared/contents-selector.component.html +++ b/src/Squidex/app/features/content/shared/contents-selector.component.html @@ -48,8 +48,8 @@
-
@@ -60,7 +60,7 @@ {{userInfo.user.email}} - + @@ -68,10 +68,12 @@ - - + + + +
- +
+ - - - {{dueTimeAction}} content item(s) - + + + {{dueTimeAction}} content item(s) + - -
- - -
+ +
+ + +
-
- - -
+
+ + +
- -
+ +
- - - - -
- + + + + +
diff --git a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html index 695bdea88..efcc0fb23 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-element.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-element.component.html @@ -12,6 +12,7 @@ +
diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index 3161d203c..ba7e7a540 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -64,9 +64,12 @@
- diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html index 509579d6e..13c866d67 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -4,6 +4,7 @@ Clone Schema + Create Schema diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index 8a04729f8..2254f8730 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -18,77 +18,75 @@ - +
Your have reached the maximum number of backups: 10.
- -
- No backups created yet. +
+ No backups created yet. - -
- -
-
-
-
- -
-
- -
-
- -
+ +
+ +
+
+
+
+
-
-
- Started: -
-
- Duration: -
+
+
-
diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.html b/src/Squidex/app/features/settings/pages/clients/client.component.html index 654781be9..476038fd9 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.html +++ b/src/Squidex/app/features/settings/pages/clients/client.component.html @@ -2,7 +2,7 @@
- +
@@ -16,13 +16,13 @@ - +

{{client.name}}

-
+
diff --git a/src/Squidex/app/features/settings/pages/languages/languages-page.component.html b/src/Squidex/app/features/settings/pages/languages/languages-page.component.html index ae44142e4..989801c71 100644 --- a/src/Squidex/app/features/settings/pages/languages/languages-page.component.html +++ b/src/Squidex/app/features/settings/pages/languages/languages-page.component.html @@ -14,14 +14,12 @@ - - - - - + + +