Browse Source

Started to refactor rule action.

pull/315/head
Sebastian Stehle 7 years ago
parent
commit
ddb80deb93
  1. 15
      Squidex.sln
  2. 24
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/AzureQueueAction.cs
  3. 32
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs
  4. 30
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  5. 29
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs
  6. 1
      src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  7. 5
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  8. 4
      src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs
  9. 173
      src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs
  10. 17
      src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaAction.cs
  11. 4
      src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaActionHandler.cs
  12. 34
      src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueAction.cs
  13. 4
      src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueActionHandler.cs
  14. 50
      src/Squidex.Domain.Apps.Rules/Actions/ClientPool.cs
  15. 25
      src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchAction.cs
  16. 4
      src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  17. 15
      src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyAction.cs
  18. 5
      src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyActionHandler.cs
  19. 0
      src/Squidex.Domain.Apps.Rules/Actions/HttpHelper.cs
  20. 36
      src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumAction.cs
  21. 8
      src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumActionHandler.cs
  22. 106
      src/Squidex.Domain.Apps.Rules/Actions/RuleActionRegistry.cs
  23. 15
      src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackAction.cs
  24. 5
      src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackActionHandler.cs
  25. 17
      src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetAction.cs
  26. 4
      src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetActionHandler.cs
  27. 2
      src/Squidex.Domain.Apps.Rules/Actions/Twitter/TwitterOptions.cs
  28. 14
      src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookAction.cs
  29. 4
      src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookActionHandler.cs
  30. 34
      src/Squidex.Domain.Apps.Rules/Squidex.Domain.Apps.Rules.csproj
  31. 30
      src/Squidex.Infrastructure/AbsoluteUrlAttribute.cs
  32. 1
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  33. 2
      src/Squidex/AppServices.cs
  34. 2
      src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
  35. 6
      src/Squidex/Areas/Api/Controllers/JsonInheritanceConverter.cs
  36. 42
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AlgoliaActionDto.cs
  37. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AzureQueueActionDto.cs
  38. 53
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/ElasticSearchActionDto.cs
  39. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/FastlyActionDto.cs
  40. 57
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumActionDto.cs
  41. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs
  42. 41
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/TweetActionDto.cs
  43. 36
      src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/WebhookActionDto.cs
  44. 68
      src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs
  45. 12
      src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs
  46. 29
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs
  47. 63
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs
  48. 20
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs
  49. 12
      src/Squidex/Areas/Api/Controllers/Rules/Models/UpdateRuleDto.cs
  50. 2
      src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs
  51. 4
      src/Squidex/Areas/Api/Controllers/UI/UIController.cs
  52. 2
      src/Squidex/Areas/Api/Views/Shared/Docs.cshtml
  53. 31
      src/Squidex/Config/Domain/RuleServices.cs
  54. 3
      src/Squidex/Config/Domain/SerializationServices.cs
  55. 1
      src/Squidex/Squidex.csproj
  56. 4
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  57. 766
      src/Squidex/app/theme/icomoon/demo.html
  58. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  59. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  60. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  61. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  62. 1401
      src/Squidex/app/theme/icomoon/selection.json
  63. 155
      src/Squidex/app/theme/icomoon/style.css
  64. 31
      tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs
  65. 33
      tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs
  66. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AlgoliaActionTests.cs
  67. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AzureQueueActionTests.cs
  68. 31
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/ElasticSearchActionTests.cs
  69. 19
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/FastlyActionTests.cs
  70. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs
  71. 39
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs
  72. 39
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/TweetActionTests.cs
  73. 19
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs
  74. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs
  75. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs
  76. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs
  77. 1
      tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

15
Squidex.sln

@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migrate_01", "tools\Migrate
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Tests", "tests\Squidex.Tests\Squidex.Tests.csproj", "{7E8CC864-4C6E-496F-A672-9F9AD8874835}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Rules", "src\Squidex.Domain.Apps.Rules\Squidex.Domain.Apps.Rules.csproj", "{99B4B165-9146-4406-87AA-A6CD722E33D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -321,6 +323,18 @@ Global
{7E8CC864-4C6E-496F-A672-9F9AD8874835}.Release|x64.Build.0 = Release|Any CPU
{7E8CC864-4C6E-496F-A672-9F9AD8874835}.Release|x86.ActiveCfg = Release|Any CPU
{7E8CC864-4C6E-496F-A672-9F9AD8874835}.Release|x86.Build.0 = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|x64.Build.0 = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|x86.ActiveCfg = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Debug|x86.Build.0 = Debug|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|Any CPU.Build.0 = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|x64.ActiveCfg = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|x64.Build.0 = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|x86.ActiveCfg = Release|Any CPU
{99B4B165-9146-4406-87AA-A6CD722E33D6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -349,6 +363,7 @@ Global
{AA003372-CD8D-4DBC-962C-F61E0C93CF05} = {C9809D59-6665-471E-AD87-5AC624C65892}
{7DA5B308-D950-4496-93D5-21D6C4D91644} = {C9809D59-6665-471E-AD87-5AC624C65892}
{A4823E14-C0E5-4A4D-B28F-27424C25C3C7} = {94207AA6-4923-4183-A558-E0F8196B8CA3}
{99B4B165-9146-4406-87AA-A6CD722E33D6} = {C9809D59-6665-471E-AD87-5AC624C65892}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08}

24
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/AzureQueueAction.cs

@ -1,24 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
{
[TypeName(nameof(AzureQueueAction))]
public sealed class AzureQueueAction : RuleAction
{
public string ConnectionString { get; set; }
public string Queue { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

32
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/MediumAction.cs

@ -1,32 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
{
[TypeName(nameof(MediumAction))]
public sealed class MediumAction : RuleAction
{
public string AccessToken { get; set; }
public string Tags { get; set; }
public string Title { get; set; }
public string CanonicalUrl { get; set; }
public string Content { get; set; }
public bool IsHtml { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

30
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs

@ -1,30 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Rules.Actions;
namespace Squidex.Domain.Apps.Core.Rules
{
public interface IRuleActionVisitor<out T>
{
T Visit(AlgoliaAction action);
T Visit(AzureQueueAction action);
T Visit(ElasticSearchAction action);
T Visit(FastlyAction action);
T Visit(MediumAction action);
T Visit(SlackAction action);
T Visit(TweetAction action);
T Visit(WebhookAction action);
}
}

29
src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs

@ -5,10 +5,37 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules
{
public abstract class RuleAction : Freezable
{
public abstract T Accept<T>(IRuleActionVisitor<T> visitor);
public IEnumerable<ValidationError> Validate()
{
var context = new ValidationContext(this);
var errors = new List<ValidationResult>();
if (!Validator.TryValidateObject(this, context, errors, true))
{
foreach (var error in errors)
{
yield return new ValidationError(error.ErrorMessage, error.MemberNames.ToArray());
}
}
foreach (var error in CustomValidate())
{
yield return error;
}
}
protected virtual IEnumerable<ValidationError> CustomValidate()
{
yield break;
}
}
}

1
src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -14,6 +14,7 @@
</PackageReference>
<PackageReference Include="Freezable.Fody" Version="1.9.1" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>

5
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -14,19 +14,14 @@
<ProjectReference Include="..\Squidex.Shared\Squidex.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="CoreTweet" Version="0.9.0.415" />
<PackageReference Include="Elasticsearch.Net" Version="6.2.0" />
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NJsonSchema" Version="9.10.67" />
<PackageReference Include="NodaTime" Version="2.3.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>

4
src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs

@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
}
else
{
var errors = await RuleActionValidator.ValidateAsync(command.Action);
var errors = command.Action.Validate();
errors.Foreach(x => x.AddTo(e));
}
@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards
if (command.Action != null)
{
var errors = await RuleActionValidator.ValidateAsync(command.Action);
var errors = command.Action.Validate();
errors.Foreach(x => x.AddTo(e));
}

173
src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs

@ -1,173 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Rules.Guards
{
public sealed class RuleActionValidator : IRuleActionVisitor<Task<IEnumerable<ValidationError>>>
{
public static Task<IEnumerable<ValidationError>> ValidateAsync(RuleAction action)
{
Guard.NotNull(action, nameof(action));
var visitor = new RuleActionValidator();
return action.Accept(visitor);
}
public Task<IEnumerable<ValidationError>> Visit(AlgoliaAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.ApiKey))
{
errors.Add(new ValidationError("Api Key is required.", nameof(action.ApiKey)));
}
if (string.IsNullOrWhiteSpace(action.AppId))
{
errors.Add(new ValidationError("Application ID is required.", nameof(action.AppId)));
}
if (string.IsNullOrWhiteSpace(action.IndexName))
{
errors.Add(new ValidationError("Index name is required.", nameof(action.IndexName)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(AzureQueueAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.ConnectionString))
{
errors.Add(new ValidationError("Connection string is required.", nameof(action.ConnectionString)));
}
if (string.IsNullOrWhiteSpace(action.Queue))
{
errors.Add(new ValidationError("Queue name is required.", nameof(action.Queue)));
}
else if (!Regex.IsMatch(action.Queue, "^[a-z][a-z0-9]{2,}(\\-[a-z0-9]+)*$"))
{
errors.Add(new ValidationError("Queue name must be valid azure queue name.", nameof(action.Queue)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(ElasticSearchAction action)
{
var errors = new List<ValidationError>();
if (action.Host == null || !action.Host.IsAbsoluteUri)
{
errors.Add(new ValidationError("Host is required and must be an absolute URL.", nameof(action.Host)));
}
if (string.IsNullOrWhiteSpace(action.IndexType))
{
errors.Add(new ValidationError("Type name is required.", nameof(action.IndexType)));
}
if (string.IsNullOrWhiteSpace(action.IndexName))
{
errors.Add(new ValidationError("Index name is required.", nameof(action.IndexName)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(FastlyAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.ApiKey))
{
errors.Add(new ValidationError("Api Key is required.", nameof(action.ApiKey)));
}
if (string.IsNullOrWhiteSpace(action.ServiceId))
{
errors.Add(new ValidationError("Service ID is required.", nameof(action.ServiceId)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(MediumAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.AccessToken))
{
errors.Add(new ValidationError("Access token is required.", nameof(action.AccessToken)));
}
if (string.IsNullOrWhiteSpace(action.Content))
{
errors.Add(new ValidationError("Content is required.", nameof(action.Content)));
}
if (string.IsNullOrWhiteSpace(action.Title))
{
errors.Add(new ValidationError("Title is required.", nameof(action.Title)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(TweetAction action)
{
var errors = new List<ValidationError>();
if (string.IsNullOrWhiteSpace(action.AccessToken))
{
errors.Add(new ValidationError("Access Token is required.", nameof(action.AccessToken)));
}
if (string.IsNullOrWhiteSpace(action.AccessSecret))
{
errors.Add(new ValidationError("Access Secret is required.", nameof(action.AccessSecret)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(SlackAction action)
{
var errors = new List<ValidationError>();
if (action.WebhookUrl == null || !action.WebhookUrl.IsAbsoluteUri)
{
errors.Add(new ValidationError("Webhook URL is required and must be an absolute URL.", nameof(action.WebhookUrl)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
public Task<IEnumerable<ValidationError>> Visit(WebhookAction action)
{
var errors = new List<ValidationError>();
if (action.Url == null || !action.Url.IsAbsoluteUri)
{
errors.Add(new ValidationError("URL is required and must be an absolute URL.", nameof(action.Url)));
}
return Task.FromResult<IEnumerable<ValidationError>>(errors);
}
}
}

17
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/AlgoliaAction.cs → src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaAction.cs

@ -5,22 +5,23 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Algolia
{
[TypeName(nameof(AlgoliaAction))]
public sealed class AlgoliaAction : RuleAction
{
[Required]
[Display(Name = "Application Id", Description = "The application ID.")]
public string AppId { get; set; }
[Required]
[Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")]
public string ApiKey { get; set; }
[Required]
[Display(Name = "Index Name", Description = "THe name of the index.")]
public string IndexName { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AlgoliaActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/Algolia/AlgoliaActionHandler.cs

@ -10,13 +10,13 @@ using System.Threading.Tasks;
using Algolia.Search;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Algolia
{
public sealed class AlgoliaJob
{

34
src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueAction.cs

@ -0,0 +1,34 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
{
public sealed class AzureQueueAction : RuleAction
{
[Required]
[Display(Name = "Connection String", Description = "The connection string to the storage account.")]
public string ConnectionString { get; set; }
[Required]
[Display(Name = "Queue", Description = "The name of the queue.")]
public string Queue { get; set; }
protected override IEnumerable<ValidationError> CustomValidate()
{
if (!string.IsNullOrWhiteSpace(Queue) && !Regex.IsMatch(Queue, "^[a-z][a-z0-9]{2,}(\\-[a-z0-9]+)*$"))
{
yield return new ValidationError("Queue must be valid azure queue name.", nameof(Queue));
}
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/AzureQueueActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/AzureQueue/AzureQueueActionHandler.cs

@ -11,13 +11,13 @@ using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.AzureQueue
{
public sealed class AzureQueueJob
{

50
src/Squidex.Domain.Apps.Rules/Actions/ClientPool.cs

@ -0,0 +1,50 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
#pragma warning disable RECS0108 // Warns about static fields in generic types
namespace Squidex.Domain.Apps.Rules.Action
{
internal sealed class ClientPool<TKey, TClient>
{
private static readonly TimeSpan TTL = TimeSpan.FromMinutes(30);
private readonly MemoryCache memoryCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
private readonly Func<TKey, Task<TClient>> factory;
public ClientPool(Func<TKey, TClient> factory)
{
this.factory = x => Task.FromResult(factory(x));
}
public ClientPool(Func<TKey, Task<TClient>> factory)
{
this.factory = factory;
}
public TClient GetClient(TKey key)
{
return GetClientAsync(key).Result;
}
public async Task<TClient> GetClientAsync(TKey key)
{
if (!memoryCache.TryGetValue<TClient>(key, out var client))
{
client = await factory(key);
memoryCache.Set(key, client, TTL);
}
return client;
}
}
}

25
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/ElasticSearchAction.cs → src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchAction.cs

@ -6,26 +6,31 @@
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
{
[TypeName(nameof(ElasticSearchAction))]
public sealed class ElasticSearchAction : RuleAction
{
[AbsoluteUrl]
[Required]
[Display(Name = "Host", Description = "The hostname of the elastic search instance or cluster.")]
public Uri Host { get; set; }
public string Username { get; set; }
public string Password { get; set; }
[Required]
[Display(Name = "Index Name", Description = "The name of the index.")]
public string IndexName { get; set; }
[Required]
[Display(Name = "Index Type", Description = "The name of the index type.")]
public string IndexType { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
[Display(Name = "Username", Description = "The optional username.")]
public string Username { get; set; }
[Display(Name = "Password", Description = "The optional password.")]
public string Password { get; set; }
}
}

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/ElasticSearchActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -9,13 +9,13 @@ using System;
using System.Threading.Tasks;
using Elasticsearch.Net;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.ElasticSearch
{
public sealed class ElasticSearchJob
{

15
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/FastlyAction.cs → src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyAction.cs

@ -5,20 +5,19 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Fastly
{
[TypeName(nameof(FastlyAction))]
public sealed class FastlyAction : RuleAction
{
[Required]
[Display(Name = "Api Key", Description = "The API key to grant access to Squidex.")]
public string ApiKey { get; set; }
[Required]
[Display(Name = "Service Id", Description = "The ID of the fastly service.")]
public string ServiceId { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

5
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/FastlyActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/Fastly/FastlyActionHandler.cs

@ -8,17 +8,18 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Fastly
{
public sealed class FastlyJob
{
public string FastlyApiKey { get; set; }
public string FastlyServiceID { get; set; }
public string Key { get; set; }

0
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/Utils/HttpHelper.cs → src/Squidex.Domain.Apps.Rules/Actions/HttpHelper.cs

36
src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumAction.cs

@ -0,0 +1,36 @@
// ==========================================================================
// 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.Rules;
namespace Squidex.Domain.Apps.Rules.Action.Medium
{
public sealed class MediumAction : RuleAction
{
[Required]
[Display(Name = "Access Token", Description = "The self issued access token.")]
public string AccessToken { get; set; }
[Required]
[Display(Name = "Title", Description = "The title, used for the url.")]
public string Title { get; set; }
[Required]
[Display(Name = "Content", Description = "The content, either html or markdown.")]
public string Content { get; set; }
[Display(Name = "Canonical Url", Description = "The original home of this content, if it was originally published elsewhere.")]
public string CanonicalUrl { get; set; }
[Display(Name = "Tags", Description = "The optional comma separated list of tags.")]
public string Tags { get; set; }
[Display(Name = "Is Html", Description = "Indicates whether the content is markdown or html.")]
public bool IsHtml { get; set; }
}
}

8
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/MediumActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/Medium/MediumActionHandler.cs

@ -5,21 +5,21 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
#pragma warning disable SA1649 // File name must match first type name
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Http;
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Rules.Action.Medium
{
public sealed class MediumJob
{

106
src/Squidex.Domain.Apps.Rules/Actions/RuleActionRegistry.cs

@ -0,0 +1,106 @@
// ==========================================================================
// 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.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Rules.Action.Algolia;
using Squidex.Domain.Apps.Rules.Action.AzureQueue;
using Squidex.Domain.Apps.Rules.Action.ElasticSearch;
using Squidex.Domain.Apps.Rules.Action.Fastly;
using Squidex.Domain.Apps.Rules.Action.Medium;
using Squidex.Domain.Apps.Rules.Action.Slack;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Rules.Actions
{
public static class RuleActionRegistry
{
private const string Suffix = "Action";
private static readonly HashSet<Type> ActionHandlerTypes = new HashSet<Type>();
private static readonly Dictionary<string, Type> ActionTypes = new Dictionary<string, Type>();
public static IReadOnlyDictionary<string, Type> Actions
{
get { return ActionTypes; }
}
public static IReadOnlyCollection<Type> ActionHandlers
{
get { return ActionHandlerTypes; }
}
static RuleActionRegistry()
{
Register<
AlgoliaAction,
AlgoliaActionHandler>();
Register<
AzureQueueAction,
AzureQueueActionHandler>();
Register<
ElasticSearchAction,
ElasticSearchActionHandler>();
Register<
FastlyAction,
FastlyActionHandler>();
Register<
MediumAction,
MediumActionHandler>();
Register<
SlackAction,
SlackActionHandler>();
Register<
TweetAction,
TweetActionHandler>();
Register<
WebhookAction,
WebhookActionHandler>();
}
public static void Register<TAction, THandler>() where TAction : RuleAction where THandler : IRuleActionHandler
{
AddActionType<TAction>();
AddActionHandler<THandler>();
}
private static void AddActionHandler<THandler>() where THandler : IRuleActionHandler
{
ActionHandlerTypes.Add(typeof(THandler));
}
private static void AddActionType<TAction>() where TAction : RuleAction
{
var name = typeof(TAction).Name;
if (name.EndsWith(Suffix, StringComparison.Ordinal))
{
name = name.Substring(0, name.Length - Suffix.Length);
}
ActionTypes.Add(name, typeof(TAction));
}
public static void RegisterTypes(TypeNameRegistry typeNameRegistry)
{
foreach (var actionType in ActionTypes.Values)
{
typeNameRegistry.Map(actionType, actionType.Name);
}
}
}
}

15
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/SlackAction.cs → src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackAction.cs

@ -6,20 +6,21 @@
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Slack
{
[TypeName(nameof(SlackAction))]
public sealed class SlackAction : RuleAction
{
[AbsoluteUrl]
[Required]
[Display(Name = "Webhook Url", Description = "The slack webhook url.")]
public Uri WebhookUrl { get; set; }
[Required]
[Display(Name = "Text", Description = "The text that is sent as message to slack.")]
public string Text { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

5
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/SlackActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/Slack/SlackActionHandler.cs

@ -11,18 +11,19 @@ using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Slack
{
public sealed class SlackJob
{
public string RequestUrl { get; set; }
public string RequestBodyV2 { get; set; }
public JObject RequestBody { get; set; }

17
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TweetAction.cs → src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetAction.cs

@ -5,22 +5,23 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Infrastructure;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Twitter
{
[TypeName(nameof(TweetAction))]
public sealed class TweetAction : RuleAction
{
[Required]
[Display(Name = "Access Token", Description = " The generated access token.")]
public string AccessToken { get; set; }
[Required]
[Display(Name = "Access Secret", Description = " The generated access secret.")]
public string AccessSecret { get; set; }
[Required]
[Display(Name = "Text", Description = "The text that is sent as tweet to twitter.")]
public string Text { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/TweetActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/Twitter/TweetActionHandler.cs

@ -9,13 +9,13 @@ using System;
using System.Threading.Tasks;
using CoreTweet;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Twitter
{
public sealed class TweetJob
{

2
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/TwitterOptions.cs → src/Squidex.Domain.Apps.Rules/Actions/Twitter/TwitterOptions.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Twitter
{
public sealed class TwitterOptions
{

14
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs → src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookAction.cs

@ -6,20 +6,20 @@
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Webhook
{
[TypeName(nameof(WebhookAction))]
public sealed class WebhookAction : RuleAction
{
[AbsoluteUrl]
[Required]
[Display(Name = "Url", Description = "he url of the webhook.")]
public Uri Url { get; set; }
[Display(Name = "Shared Secret", Description = "The shared secret that is used to calculate the signature.")]
public string SharedSecret { get; set; }
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
}

4
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs → src/Squidex.Domain.Apps.Rules/Actions/WebhookAction/WebhookActionHandler.cs

@ -11,14 +11,14 @@ using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions.Utils;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure;
#pragma warning disable SA1649 // File name must match first type name
namespace Squidex.Domain.Apps.Core.HandleRules.Actions
namespace Squidex.Domain.Apps.Rules.Action.Webhook
{
public sealed class WebhookJob
{

34
src/Squidex.Domain.Apps.Rules/Squidex.Domain.Apps.Rules.csproj

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Model\Squidex.Domain.Apps.Core.Model.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="5.1.0" />
<PackageReference Include="CoreTweet" Version="0.9.0.415" />
<PackageReference Include="Elasticsearch.Net" Version="6.2.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NodaTime" Version="2.3.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.1" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

30
src/Squidex.Infrastructure/AbsoluteUrlAttribute.cs

@ -0,0 +1,30 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
namespace Squidex.Infrastructure
{
public sealed class AbsoluteUrlAttribute : ValidationAttribute
{
public AbsoluteUrlAttribute()
: base(() => "The {0} field must be an absolute URL.")
{
}
public override bool IsValid(object value)
{
if (value is Uri uri && !uri.IsAbsoluteUri)
{
return false;
}
return true;
}
}
}

1
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -19,6 +19,7 @@
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0004" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Reactive" Version="4.0.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.5.0" />

2
src/Squidex/AppServices.cs

@ -14,7 +14,7 @@ using Squidex.Config;
using Squidex.Config.Authentication;
using Squidex.Config.Domain;
using Squidex.Config.Web;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using Squidex.Infrastructure.Commands;
namespace Squidex

2
src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs

@ -15,6 +15,7 @@ using NSwag.AspNetCore;
using NSwag.SwaggerGeneration;
using NSwag.SwaggerGeneration.Processors.Security;
using Squidex.Areas.Api.Controllers.Contents.Generator;
using Squidex.Areas.Api.Controllers.Rules.Models;
using Squidex.Config;
using Squidex.Infrastructure;
using Squidex.Pipeline.Swagger;
@ -102,6 +103,7 @@ namespace Squidex.Areas.Api.Config.Swagger
new PrimitiveTypeMapper(typeof(RefToken), s => s.Type = JsonObjectType.String)
};
settings.GeneratorSettings.DocumentProcessors.Add(new RuleActionProcessor());
settings.GeneratorSettings.DocumentProcessors.Add(new XmlTagProcessor());
settings.GeneratorSettings.OperationProcessors.Add(new XmlTagProcessor());

6
src/Squidex/Areas/Api/Controllers/JsonInheritanceConverter.cs

@ -29,6 +29,11 @@ namespace Squidex.Areas.Api.Controllers
[ThreadStatic]
private static bool IsWriting;
public string DiscriminatorName
{
get { return discriminator; }
}
public override bool CanWrite
{
get
@ -64,6 +69,7 @@ namespace Squidex.Areas.Api.Controllers
var name = type.GetTypeInfo().GetCustomAttribute<JsonSchemaAttribute>()?.Name ?? type.Name;
mapTypeToName[type] = name;
mapNameToType[name] = type;
}
}

42
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AlgoliaActionDto.cs

@ -1,42 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Algolia")]
public sealed class AlgoliaActionDto : RuleActionDto
{
/// <summary>
/// The application ID.
/// </summary>
[Required]
public string AppId { get; set; }
/// <summary>
/// The API key to grant access to Squidex.
/// </summary>
[Required]
public string ApiKey { get; set; }
/// <summary>
/// The name of the index.
/// </summary>
[Required]
public string IndexName { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new AlgoliaAction());
}
}
}

36
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/AzureQueueActionDto.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("AzureQueue")]
public class AzureQueueActionDto : RuleActionDto
{
/// <summary>
/// The connection string to the storage account.
/// </summary>
[Required]
public string ConnectionString { get; set; }
/// <summary>
/// The queue name.
/// </summary>
[Required]
public string Queue { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new AzureQueueAction());
}
}
}

53
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/ElasticSearchActionDto.cs

@ -1,53 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("ElasticSearch")]
public sealed class ElasticSearchActionDto : RuleActionDto
{
/// <summary>
/// The host to the elastic search instance.
/// </summary>
[Required]
public Uri Host { get; set; }
/// <summary>
/// The name of the index.
/// </summary>
[Required]
public string IndexName { get; set; }
/// <summary>
/// The name of the index type.
/// </summary>
[Required]
public string IndexType { get; set; }
/// <summary>
/// The optional username for authentication.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The optional password for authentication.
/// </summary>
public string Password { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new ElasticSearchAction());
}
}
}

36
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/FastlyActionDto.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Fastly")]
public sealed class FastlyActionDto : RuleActionDto
{
/// <summary>
/// The ID of the fastly service.
/// </summary>
[Required]
public string ServiceId { get; set; }
/// <summary>
/// The API key to grant access to Squidex.
/// </summary>
[Required]
public string ApiKey { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new FastlyAction());
}
}
}

57
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/MediumActionDto.cs

@ -1,57 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Medium")]
public class MediumActionDto : RuleActionDto
{
/// <summary>
/// The self issued access token.
/// </summary>
[Required]
public string AccessToken { get; set; }
/// <summary>
/// The optional comma separated list of tags.
/// </summary>
public string Tags { get; set; }
/// <summary>
/// The title, used for the url.
/// </summary>
[Required]
public string Title { get; set; }
/// <summary>
/// The content, either html or markdown.
/// </summary>
[Required]
public string Content { get; set; }
/// <summary>
/// The original home of this content, if it was originally published elsewhere.
/// </summary>
public string CanonicalUrl { get; set; }
/// <summary>
/// Indicates whether the content is markdown or html.
/// </summary>
public bool IsHtml { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new MediumAction());
}
}
}

36
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/SlackActionDto.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Slack")]
public sealed class SlackActionDto : RuleActionDto
{
/// <summary>
/// The slack webhook url.
/// </summary>
[Required]
public Uri WebhookUrl { get; set; }
/// <summary>
/// The text that is sent as message to slack.
/// </summary>
public string Text { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new SlackAction());
}
}
}

41
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/TweetActionDto.cs

@ -1,41 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Tweet")]
public sealed class TweetActionDto : RuleActionDto
{
/// <summary>
/// The access token.
/// </summary>
[Required]
public string AccessToken { get; set; }
/// <summary>
/// The access secret.
/// </summary>
[Required]
public string AccessSecret { get; set; }
/// <summary>
/// The text that is sent as tweet to twitter.
/// </summary>
public string Text { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new TweetAction());
}
}
}

36
src/Squidex/Areas/Api/Controllers/Rules/Models/Actions/WebhookActionDto.cs

@ -1,36 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using NJsonSchema.Annotations;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Actions
{
[JsonSchema("Webhook")]
public sealed class WebhookActionDto : RuleActionDto
{
/// <summary>
/// The url of the rule.
/// </summary>
[Required]
public Uri Url { get; set; }
/// <summary>
/// The shared secret that is used to calculate the signature.
/// </summary>
public string SharedSecret { get; set; }
public override RuleAction ToAction()
{
return SimpleMapper.Map(this, new WebhookAction());
}
}
}

68
src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleActionDtoFactory.cs

@ -1,68 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Areas.Api.Controllers.Rules.Models.Actions;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters
{
public sealed class RuleActionDtoFactory : IRuleActionVisitor<RuleActionDto>
{
private static readonly RuleActionDtoFactory Instance = new RuleActionDtoFactory();
private RuleActionDtoFactory()
{
}
public static RuleActionDto Create(RuleAction properties)
{
return properties.Accept(Instance);
}
public RuleActionDto Visit(AlgoliaAction action)
{
return SimpleMapper.Map(action, new AlgoliaActionDto());
}
public RuleActionDto Visit(AzureQueueAction action)
{
return SimpleMapper.Map(action, new AzureQueueActionDto());
}
public RuleActionDto Visit(ElasticSearchAction action)
{
return SimpleMapper.Map(action, new ElasticSearchActionDto());
}
public RuleActionDto Visit(FastlyAction action)
{
return SimpleMapper.Map(action, new FastlyActionDto());
}
public RuleActionDto Visit(MediumAction action)
{
return SimpleMapper.Map(action, new MediumActionDto());
}
public RuleActionDto Visit(SlackAction action)
{
return SimpleMapper.Map(action, new SlackActionDto());
}
public RuleActionDto Visit(TweetAction action)
{
return SimpleMapper.Map(action, new TweetActionDto());
}
public RuleActionDto Visit(WebhookAction action)
{
return SimpleMapper.Map(action, new WebhookActionDto());
}
}
}

12
src/Squidex/Areas/Api/Controllers/Rules/Models/CreateRuleDto.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands;
namespace Squidex.Areas.Api.Controllers.Rules.Models
@ -22,16 +24,12 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// The action properties.
/// </summary>
[Required]
public RuleActionDto Action { get; set; }
[JsonConverter(typeof(JsonInheritanceConverter), "actionType", typeof(RuleAction))]
public RuleAction Action { get; set; }
public CreateRule ToCommand()
{
var command = new CreateRule();
if (Action != null)
{
command.Action = Action.ToAction();
}
var command = new CreateRule { Action = Action };
if (Trigger != null)
{

29
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionDto.cs

@ -1,29 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Linq;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
[JsonConverter(typeof(JsonInheritanceConverter), "actionType", typeof(RuleActionDto))]
[KnownType(nameof(Subtypes))]
public abstract class RuleActionDto
{
public abstract RuleAction ToAction();
public static Type[] Subtypes()
{
var type = typeof(RuleActionDto);
return type.Assembly.GetTypes().Where(type.IsAssignableFrom).ToArray();
}
}
}

63
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleActionProcessor.cs

@ -0,0 +1,63 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using NJsonSchema;
using NSwag.SwaggerGeneration.Processors;
using NSwag.SwaggerGeneration.Processors.Contexts;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Rules.Actions;
namespace Squidex.Areas.Api.Controllers.Rules.Models
{
public sealed class RuleActionProcessor : IDocumentProcessor
{
public async Task ProcessAsync(DocumentProcessorContext context)
{
var schema = context.SchemaResolver.GetSchema(typeof(RuleAction), false);
if (schema != null)
{
var discriminator = new OpenApiDiscriminator
{
JsonInheritanceConverter = new JsonInheritanceConverter("actionType", typeof(RuleAction)),
PropertyName = "actionType"
};
schema.DiscriminatorObject = discriminator;
schema.Properties["actionType"] = new JsonProperty
{
Type = JsonObjectType.String,
IsRequired = true
};
foreach (var derived in RuleActionRegistry.Actions)
{
var derivedSchema = await context.SchemaGenerator.GenerateAsync(derived.Value, 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);
}
}
RemoveFreezable(context, schema);
}
}
private static void RemoveFreezable(DocumentProcessorContext context, JsonSchema4 schema)
{
context.Document.Definitions.Remove("Freezable");
schema.AllOf.Clear();
}
}
}

20
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs

@ -7,8 +7,10 @@
using System;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using NodaTime;
using Squidex.Areas.Api.Controllers.Rules.Models.Converters;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
@ -49,6 +51,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// </summary>
public int Version { get; set; }
/// <summary>
/// Determines if the rule is enabled.
/// </summary>
public bool IsEnabled { get; set; }
/// <summary>
/// The trigger properties.
/// </summary>
@ -59,12 +66,8 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// The action properties.
/// </summary>
[Required]
public RuleActionDto Action { get; set; }
/// <summary>
/// Determines if the rule is enabled.
/// </summary>
public bool IsEnabled { get; set; }
[JsonConverter(typeof(JsonInheritanceConverter), "actionType", typeof(RuleAction))]
public RuleAction Action { get; set; }
public static RuleDto FromRule(IRuleEntity rule)
{
@ -78,11 +81,6 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
response.Trigger = RuleTriggerDtoFactory.Create(rule.RuleDef.Trigger);
}
if (rule.RuleDef.Action != null)
{
response.Action = RuleActionDtoFactory.Create(rule.RuleDef.Action);
}
return response;
}
}

12
src/Squidex/Areas/Api/Controllers/Rules/Models/UpdateRuleDto.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands;
namespace Squidex.Areas.Api.Controllers.Rules.Models
@ -20,16 +22,12 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
/// <summary>
/// The action properties.
/// </summary>
public RuleActionDto Action { get; set; }
[JsonConverter(typeof(JsonInheritanceConverter), "actionType", typeof(RuleAction))]
public RuleAction Action { get; set; }
public UpdateRule ToCommand(Guid id)
{
var command = new UpdateRule { RuleId = id };
if (Action != null)
{
command.Action = Action.ToAction();
}
var command = new UpdateRule { RuleId = id, Action = Action };
if (Trigger != null)
{

2
src/Squidex/Areas/Api/Controllers/Rules/TwitterController.cs

@ -8,7 +8,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using static CoreTweet.OAuth;
namespace Squidex.Areas.Api.Controllers.Rules

4
src/Squidex/Areas/Api/Controllers/UI/UIController.cs

@ -12,8 +12,8 @@ using NSwag.Annotations;
using Orleans;
using Squidex.Areas.Api.Controllers.UI.Models;
using Squidex.Config;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using Squidex.Infrastructure.Commands;
using Squidex.Pipeline;
@ -71,7 +71,7 @@ namespace Squidex.Areas.Api.Controllers.UI
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="key">The name of the setting.</param>
/// <param name="value">The name of the value.</param>
/// <param name="request">The request with the value to update.</param>
/// <returns>
/// 200 => UI setting set.
/// 404 => App not found.

2
src/Squidex/Areas/Api/Views/Shared/Docs.cshtml

@ -19,6 +19,6 @@
<body>
<redoc spec-url="@Url.Content(Model.Specification)"></redoc>
<script src="https://unpkg.com/redoc@2.0.0-alpha.3/bundles/redoc.standalone.js"></script>
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.21/bundles/redoc.standalone.js"></script>
</body>
</html>

31
src/Squidex/Config/Domain/RuleServices.cs

@ -7,9 +7,9 @@
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Actions;
using Squidex.Domain.Apps.Core.HandleRules.Triggers;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Rules.Actions;
using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Config.Domain
@ -27,30 +27,6 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentChangedTriggerHandler>()
.As<IRuleTriggerHandler>();
services.AddSingletonAs<AlgoliaActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<AzureQueueActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<ElasticSearchActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<FastlyActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<MediumActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<TweetActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<SlackActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<WebhookActionHandler>()
.As<IRuleActionHandler>();
services.AddSingletonAs<RuleEnqueuer>()
.As<IEventConsumer>();
@ -59,6 +35,11 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<RuleService>()
.AsSelf();
foreach (var actionHandler in RuleActionRegistry.ActionHandlers)
{
services.AddSingleton(typeof(IRuleActionHandler), actionHandler);
}
}
}
}

3
src/Squidex/Config/Domain/SerializationServices.cs

@ -17,6 +17,7 @@ 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.Domain.Apps.Rules.Actions;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
@ -38,6 +39,8 @@ namespace Squidex.Config.Domain
private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling)
{
RuleActionRegistry.RegisterTypes(TypeNameRegistry);
settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry);
settings.ContractResolver = new ConverterContractResolver(

1
src/Squidex/Squidex.csproj

@ -40,6 +40,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Entities.MongoDb\Squidex.Domain.Apps.Entities.MongoDb.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Rules\Squidex.Domain.Apps.Rules.csproj" />
<ProjectReference Include="..\Squidex.Domain.Users\Squidex.Domain.Users.csproj" />
<ProjectReference Include="..\Squidex.Domain.Users.MongoDb\Squidex.Domain.Users.MongoDb.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure.Azure\Squidex.Infrastructure.Azure.csproj" />

4
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -150,10 +150,10 @@ p {
font-size: 32px;
}
.fs2 {
font-size: 24px;
font-size: 32px;
}
.fs3 {
font-size: 32px;
font-size: 24px;
}
.fs4 {
font-size: 28px;

766
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -102,6 +102,7 @@
<glyph unicode="&#xe95c;" glyph-name="twitter, action-Tweet" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xe95d;" glyph-name="star-full" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
<glyph unicode="&#xe95e;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe95f;" glyph-name="action-Discourse" d="M516.375 960c-280.342 0-516.375-225.198-516.375-503.070v-520.93l516.278 0.501c280.342 0 507.722 233.87 507.722 511.644s-227.581 511.855-507.625 511.855zM527.127 766.031c60.539-1.361 116.545-19.455 163.822-49.771l-1.231 0.74c1.255-0.811 1.433-0.931 1.61-1.052l-1.066 0.686c9.209-5.848 16.936-11.303 24.393-17.085l-0.691 0.516c1.847-1.424 3.017-2.351 4.181-3.287l-0.584 0.455c6.987-5.558 13.161-10.888 19.117-16.446l-0.159 0.147c2.158-2.024 4.147-3.94 6.111-5.881l-0.005 0.005c4.962-4.875 9.74-9.891 14.349-15.059l0.293-0.334c2.34-2.624 4.921-5.64 7.445-8.705l0.449-0.562c1.711-1.93 3.701-4.249 5.657-6.598l0.429-0.53c-0.418 0.387-1.038 0.952-1.661 1.516l-0.245 0.218c3.336-4.033 6.991-8.738 10.511-13.546l0.63-0.903c0.029-0.029 0.036-0.036 0.042-0.042l-0.023 0.023c-0.306 0.565 0.151-0.158 0.604-0.883l0.776-1.333c4.635-6.582 9.622-14.446 14.255-22.537l0.814-1.546c4.177-7.246 8.751-16.326 12.879-25.645l0.79-2.001c16.218-36.345 25.659-78.731 25.659-123.301 0-172.151-140.846-311.706-314.589-311.706-46.906 0-91.415 10.172-131.402 28.411l1.955-0.799-204.493-45.776 56.952 181.504c-2.82 4.996-6.227 11.776-9.386 18.691l-0.802 1.962c-2.441 5.14-5.422 12.383-8.131 19.756l-0.696 2.168c-0.067-0.083-0.856 1.957-1.625 4.007l-0.709 2.158c-2.628 7.191-5.486 16.773-7.88 26.535l-0.442 2.132c-5.397 21.611-8.492 46.422-8.492 71.949 0 172.205 140.891 311.805 314.688 311.805 2.075 0 4.144-0.020 6.209-0.059l-0.309 0.005z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 82 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

1401
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because it is too large

155
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?jk799');
src: url('fonts/icomoon.eot?jk799#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?jk799') format('truetype'),
url('fonts/icomoon.woff?jk799') format('woff'),
url('fonts/icomoon.svg?jk799#icomoon') format('svg');
src: url('fonts/icomoon.eot?it2nng');
src: url('fonts/icomoon.eot?it2nng#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?it2nng') format('truetype'),
url('fonts/icomoon.woff?it2nng') format('woff'),
url('fonts/icomoon.svg?it2nng#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,77 +24,8 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-star-full:before {
content: "\e95d";
}
.icon-star-empty:before {
content: "\e95e";
}
.icon-twitter:before {
content: "\e95c";
}
.icon-action-Tweet:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-backup:before {
content: "\e95b";
}
.icon-support:before {
content: "\e95a";
}
.icon-control-RichText:before {
content: "\e939";
}
.icon-download:before {
content: "\e93e";
.icon-action-Discourse:before {
content: "\e95f";
}
.icon-action-Medium:before {
content: "\e959";
@ -258,6 +189,78 @@
.icon-user:before {
content: "\e928";
}
.icon-star-full:before {
content: "\e95d";
}
.icon-star-empty:before {
content: "\e95e";
}
.icon-twitter:before {
content: "\e95c";
}
.icon-action-Tweet:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-action-AzureQueue:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-backup:before {
content: "\e95b";
}
.icon-support:before {
content: "\e95a";
}
.icon-control-RichText:before {
content: "\e939";
}
.icon-download:before {
content: "\e93e";
}
.icon-single-content:before {
content: "\e958";
}

31
tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs

@ -12,7 +12,6 @@ using FluentAssertions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Xunit;
@ -24,13 +23,6 @@ namespace Squidex.Domain.Apps.Core.Model.Rules
{
private readonly JsonSerializer serializer = TestData.DefaultSerializer();
public static readonly List<object[]> Actions =
typeof(Rule).Assembly.GetTypes()
.Where(x => x.BaseType == typeof(RuleAction))
.Select(Activator.CreateInstance)
.Select(x => new object[] { x })
.ToList();
public static readonly List<object[]> Triggers =
typeof(Rule).Assembly.GetTypes()
.Where(x => x.BaseType == typeof(RuleTrigger))
@ -38,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Model.Rules
.Select(x => new object[] { x })
.ToList();
private readonly Rule rule_0 = new Rule(new ContentChangedTrigger(), new WebhookAction());
private readonly Rule rule_0 = new Rule(new ContentChangedTrigger(), new FirstAction());
public sealed class OtherTrigger : RuleTrigger
{
@ -48,19 +40,21 @@ namespace Squidex.Domain.Apps.Core.Model.Rules
}
}
public sealed class FirstAction : RuleAction
{
public string Property { get; set; }
}
public sealed class OtherAction : RuleAction
{
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
throw new NotSupportedException();
}
public string Property { get; set; }
}
[Fact]
public void Should_create_with_trigger_and_action()
{
var ruleTrigger = new ContentChangedTrigger();
var ruleAction = new WebhookAction();
var ruleAction = new FirstAction();
var newRule = new Rule(ruleTrigger, ruleAction);
@ -110,7 +104,7 @@ namespace Squidex.Domain.Apps.Core.Model.Rules
[Fact]
public void Should_replace_action_when_updating()
{
var newAction = new WebhookAction();
var newAction = new FirstAction();
var rule_1 = rule_0.Update(newAction);
@ -134,13 +128,6 @@ namespace Squidex.Domain.Apps.Core.Model.Rules
appClients.Should().BeEquivalentTo(rule_0);
}
[Theory]
[MemberData(nameof(Actions))]
public void Should_freeze_actions(RuleAction action)
{
TestData.TestFreeze(action);
}
[Theory]
[MemberData(nameof(Triggers))]
public void Should_freeze_triggers(RuleTrigger trigger)

33
tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs

@ -13,7 +13,6 @@ using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Contents;
@ -38,12 +37,12 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
{
}
public sealed class ValidAction : RuleAction
{
}
public sealed class InvalidAction : RuleAction
{
public override T Accept<T>(IRuleActionVisitor<T> visitor)
{
return default(T);
}
}
public sealed class InvalidTrigger : RuleTrigger
@ -57,13 +56,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
public RuleServiceTests()
{
typeNameRegistry.Map(typeof(ContentCreated));
typeNameRegistry.Map(typeof(WebhookAction));
typeNameRegistry.Map(typeof(ValidAction), "ValidAction");
A.CallTo(() => eventEnricher.EnrichAsync(A<Envelope<AppEvent>>.Ignored))
.Returns(new EnrichedContentEvent());
A.CallTo(() => ruleActionHandler.ActionType)
.Returns(typeof(WebhookAction));
.Returns(typeof(ValidAction));
A.CallTo(() => ruleTriggerHandler.TriggerType)
.Returns(typeof(ContentChangedTrigger));
@ -74,7 +73,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public async Task Should_not_create_job_for_invalid_event()
{
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction());
var ruleEnvelope = Envelope.Create(new InvalidEvent());
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -85,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public async Task Should_not_create_job_if_no_trigger_handler_registered()
{
var ruleConfig = new Rule(new InvalidTrigger(), new WebhookAction());
var ruleConfig = new Rule(new InvalidTrigger(), new ValidAction());
var ruleEnvelope = Envelope.Create(new ContentCreated());
var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope);
@ -107,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Fact]
public async Task Should_not_create_if_not_triggered()
{
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction());
var ruleEnvelope = Envelope.Create(new ContentCreated());
A.CallTo(() => ruleTriggerHandler.Triggers(A<Envelope<AppEvent>>.Ignored, ruleConfig.Trigger))
@ -125,7 +124,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var now = SystemClock.Instance.GetCurrentInstant();
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction());
var ruleEnvelope = Envelope.Create(e);
ruleEnvelope.SetTimestamp(now.Minus(Duration.FromDays(3)));
@ -154,12 +153,12 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var now = SystemClock.Instance.GetCurrentInstant();
var ruleConfig = new Rule(new ContentChangedTrigger(), new WebhookAction());
var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction());
var ruleEnvelope = Envelope.Create(e);
ruleEnvelope.SetTimestamp(now);
var actionName = "WebhookAction";
var actionName = "ValidAction";
var actionData = new JObject();
var actionDescription = "MyDescription";
@ -197,7 +196,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob))
.Returns((actionDump, null));
var result = await sut.InvokeAsync("WebhookAction", ruleJob);
var result = await sut.InvokeAsync("ValidAction", ruleJob);
Assert.Equal(RuleResult.Success, result.Result);
@ -216,7 +215,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob))
.Returns((actionDump, new InvalidOperationException()));
var result = await sut.InvokeAsync("WebhookAction", ruleJob);
var result = await sut.InvokeAsync("ValidAction", ruleJob);
Assert.Equal(RuleResult.Failed, result.Result);
@ -235,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob))
.Returns((actionDump, new TimeoutException()));
var result = await sut.InvokeAsync("WebhookAction", ruleJob);
var result = await sut.InvokeAsync("ValidAction", ruleJob);
Assert.Equal(RuleResult.Timeout, result.Result);
@ -253,7 +252,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob))
.Throws(ruleEx);
var result = await sut.InvokeAsync("WebhookAction", ruleJob);
var result = await sut.InvokeAsync("ValidAction", ruleJob);
Assert.Equal((ruleEx.ToString(), RuleResult.Failed, TimeSpan.Zero), result);
}

25
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AlgoliaActionTests.cs

@ -6,9 +6,8 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Algolia;
using Squidex.Infrastructure;
using Xunit;
@ -17,53 +16,53 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class AlgoliaActionTests
{
[Fact]
public async Task Should_add_error_if_app_id_not_defined()
public void Should_add_error_if_app_id_not_defined()
{
var action = new AlgoliaAction { AppId = null, ApiKey = "KEY", IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Application ID is required.", "AppId")
new ValidationError("The Application Id field is required.", "AppId")
});
}
[Fact]
public async Task Should_add_error_if_api_key_not_defined()
public void Should_add_error_if_api_key_not_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = null, IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Api Key is required.", "ApiKey")
new ValidationError("The Api Key field is required.", "ApiKey")
});
}
[Fact]
public async Task Should_add_error_if_index_name_not_defined()
public void Should_add_error_if_index_name_not_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = "KEY", IndexName = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Index name is required.", "IndexName")
new ValidationError("The Index Name field is required.", "IndexName")
});
}
[Fact]
public async Task Should_not_add_error_everything_defined()
public void Should_not_add_error_everything_defined()
{
var action = new AlgoliaAction { AppId = "APP", ApiKey = "KEY", IndexName = "IDX" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

25
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/AzureQueueActionTests.cs

@ -6,9 +6,8 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.AzureQueue;
using Squidex.Infrastructure;
using Xunit;
@ -17,53 +16,53 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class AzureQueueActionTests
{
[Fact]
public async Task Should_add_error_if_connection_string_is_null()
public void Should_add_error_if_connection_string_is_null()
{
var action = new AzureQueueAction { ConnectionString = null, Queue = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Connection string is required.", "ConnectionString")
new ValidationError("The Connection String field is required.", "ConnectionString")
});
}
[Fact]
public async Task Should_add_error_if_queue_is_null()
public void Should_add_error_if_queue_is_null()
{
var action = new AzureQueueAction { ConnectionString = "connection", Queue = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Queue name is required.", "Queue")
new ValidationError("The Queue field is required.", "Queue")
});
}
[Fact]
public async Task Should_add_error_if_queue_is_invalid()
public void Should_add_error_if_queue_is_invalid()
{
var action = new AzureQueueAction { ConnectionString = "connection", Queue = "Squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Queue name must be valid azure queue name.", "Queue")
new ValidationError("Queue must be valid azure queue name.", "Queue")
});
}
[Fact]
public async Task Should_not_add_error_if_values_are_valid()
public void Should_not_add_error_if_values_are_valid()
{
var action = new AzureQueueAction { ConnectionString = "connection", Queue = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

31
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/ElasticSearchActionTests.cs

@ -7,9 +7,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.ElasticSearch;
using Squidex.Infrastructure;
using Xunit;
@ -18,67 +17,67 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class ElasticSearchActionTests
{
[Fact]
public async Task Should_add_error_if_host_is_null()
public void Should_add_error_if_host_is_null()
{
var action = new ElasticSearchAction { Host = null, IndexName = "squidex", IndexType = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Host is required and must be an absolute URL.", "Host")
new ValidationError("The Host field is required.", "Host")
});
}
[Fact]
public async Task Should_add_error_if_host_is_relative()
public void Should_add_error_if_host_is_relative()
{
var action = new ElasticSearchAction { Host = new Uri("/rel", UriKind.Relative), IndexName = "squidex", IndexType = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Host is required and must be an absolute URL.", "Host")
new ValidationError("The Host field must be an absolute URL.", "Host")
});
}
[Fact]
public async Task Should_add_error_if_index_name_is_null()
public void Should_add_error_if_index_name_is_null()
{
var action = new ElasticSearchAction { Host = new Uri("http://host", UriKind.Absolute), IndexName = null, IndexType = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Index name is required.", "IndexName")
new ValidationError("The Index Name field is required.", "IndexName")
});
}
[Fact]
public async Task Should_add_error_if_index_type_is_null()
public void Should_add_error_if_index_type_is_null()
{
var action = new ElasticSearchAction { Host = new Uri("http://host", UriKind.Absolute), IndexName = "squidex", IndexType = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Type name is required.", "IndexType")
new ValidationError("The Index Type field is required.", "IndexType")
});
}
[Fact]
public async Task Should_not_add_error_if_values_are_valid()
public void Should_not_add_error_if_values_are_valid()
{
var action = new ElasticSearchAction { Host = new Uri("http://host", UriKind.Absolute), IndexName = "squidex", IndexType = "squidex" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

19
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/FastlyActionTests.cs

@ -6,9 +6,8 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Fastly;
using Squidex.Infrastructure;
using Xunit;
@ -17,39 +16,39 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class FastlyActionTests
{
[Fact]
public async Task Should_add_error_if_service_id_not_defined()
public void Should_add_error_if_service_id_not_defined()
{
var action = new FastlyAction { ServiceId = null, ApiKey = "KEY" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Service ID is required.", "ServiceId")
new ValidationError("The Service Id field is required.", "ServiceId")
});
}
[Fact]
public async Task Should_add_error_if_api_key_not_defined()
public void Should_add_error_if_api_key_not_defined()
{
var action = new FastlyAction { ServiceId = "APP", ApiKey = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Api Key is required.", "ApiKey")
new ValidationError("The Api Key field is required.", "ApiKey")
});
}
[Fact]
public async Task Should_not_add_error_everything_defined()
public void Should_not_add_error_everything_defined()
{
var action = new FastlyAction { ServiceId = "APP", ApiKey = "KEY" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

25
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/MediumActionTests.cs

@ -6,9 +6,8 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Medium;
using Squidex.Infrastructure;
using Xunit;
@ -17,53 +16,53 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class MediumActionTests
{
[Fact]
public async Task Should_add_error_if_access_token_is_null()
public void Should_add_error_if_access_token_is_null()
{
var action = new MediumAction { AccessToken = null, Title = "title", Content = "content" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Access token is required.", "AccessToken")
new ValidationError("The Access Token field is required.", "AccessToken")
});
}
[Fact]
public async Task Should_add_error_if_title_null()
public void Should_add_error_if_title_null()
{
var action = new MediumAction { AccessToken = "token", Title = null, Content = "content" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Title is required.", "Title")
new ValidationError("The Title field is required.", "Title")
});
}
[Fact]
public async Task Should_add_error_if_content_is_null()
public void Should_add_error_if_content_is_null()
{
var action = new MediumAction { AccessToken = "token", Title = "title", Content = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Content is required.", "Content")
new ValidationError("The Content field is required.", "Content")
});
}
[Fact]
public async Task Should_not_add_error_if_values_are_valid()
public void Should_not_add_error_if_values_are_valid()
{
var action = new MediumAction { AccessToken = "token", Title = "title", Content = "content" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

39
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/SlackActionTests.cs

@ -7,9 +7,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Slack;
using Squidex.Infrastructure;
using Xunit;
@ -18,39 +17,53 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class SlackActionTests
{
[Fact]
public async Task Should_add_error_if_webhook_url_is_null()
public void Should_add_error_if_webhook_url_is_null()
{
var action = new SlackAction { WebhookUrl = null };
var action = new SlackAction { WebhookUrl = null, Text = "text" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Webhook URL is required and must be an absolute URL.", "WebhookUrl")
new ValidationError("The Webhook Url field is required.", "WebhookUrl")
});
}
[Fact]
public async Task Should_add_error_if_webhook_url_is_relative()
public void Should_add_error_if_webhook_url_is_relative()
{
var action = new SlackAction { WebhookUrl = new Uri("/invalid", UriKind.Relative) };
var action = new SlackAction { WebhookUrl = new Uri("/invalid", UriKind.Relative), Text = "text" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Webhook URL is required and must be an absolute URL.", "WebhookUrl")
new ValidationError("The Webhook Url field must be an absolute URL.", "WebhookUrl")
});
}
[Fact]
public async Task Should_not_add_error_if_webhook_url_is_absolute()
public void Should_add_error_if_text_is_null()
{
var action = new SlackAction { WebhookUrl = new Uri("https://squidex.io", UriKind.Absolute) };
var action = new SlackAction { WebhookUrl = new Uri("https://squidex.io", UriKind.Absolute), Text = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("The Text field is required.", "Text")
});
}
[Fact]
public void Should_not_add_error_if_webhook_url_is_absolute()
{
var action = new SlackAction { WebhookUrl = new Uri("https://squidex.io", UriKind.Absolute), Text = "text" };
var errors = action.Validate();
Assert.Empty(errors);
}

39
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/TweetActionTests.cs

@ -6,9 +6,8 @@
// ==========================================================================
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Twitter;
using Squidex.Infrastructure;
using Xunit;
@ -17,39 +16,53 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class TweetActionTests
{
[Fact]
public async Task Should_add_error_if_access_token_is_null()
public void Should_add_error_if_access_token_is_null()
{
var action = new TweetAction { AccessToken = null, AccessSecret = "secret" };
var action = new TweetAction { AccessToken = null, AccessSecret = "secret", Text = "text" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Access Token is required.", "AccessToken")
new ValidationError("The Access Token field is required.", "AccessToken")
});
}
[Fact]
public async Task Should_add_error_if_access_secret_is_null()
public void Should_add_error_if_access_secret_is_null()
{
var action = new TweetAction { AccessToken = "token", AccessSecret = null };
var action = new TweetAction { AccessToken = "token", AccessSecret = null, Text = "text" };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("Access Secret is required.", "AccessSecret")
new ValidationError("The Access Secret field is required.", "AccessSecret")
});
}
[Fact]
public async Task Should_not_add_error_if_access_token_and_secret_defined()
public void Should_add_error_if_text_is_null()
{
var action = new TweetAction { AccessToken = "token", AccessSecret = "secret" };
var action = new TweetAction { AccessToken = "token", AccessSecret = "secret", Text = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("The Text field is required.", "Text")
});
}
[Fact]
public void Should_not_add_error_if_access_token_and_secret_defined()
{
var action = new TweetAction { AccessToken = "token", AccessSecret = "secret", Text = "text" };
var errors = action.Validate();
Assert.Empty(errors);
}

19
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs

@ -7,9 +7,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluentAssertions;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
using Xunit;
@ -18,39 +17,39 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions
public class WebhookActionTests
{
[Fact]
public async Task Should_add_error_if_url_is_null()
public void Should_add_error_if_url_is_null()
{
var action = new WebhookAction { Url = null };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("URL is required and must be an absolute URL.", "Url")
new ValidationError("The Url field is required.", "Url")
});
}
[Fact]
public async Task Should_add_error_if_url_is_relative()
public void Should_add_error_if_url_is_relative()
{
var action = new WebhookAction { Url = new Uri("/invalid", UriKind.Relative) };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
errors.Should().BeEquivalentTo(
new List<ValidationError>
{
new ValidationError("URL is required and must be an absolute URL.", "Url")
new ValidationError("The Url field must be an absolute URL.", "Url")
});
}
[Fact]
public async Task Should_not_add_error_if_url_is_absolute()
public void Should_not_add_error_if_url_is_absolute()
{
var action = new WebhookAction { Url = new Uri("https://squidex.io", UriKind.Absolute) };
var errors = await RuleActionValidator.ValidateAsync(action);
var errors = action.Validate();
Assert.Empty(errors);
}

2
tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs

@ -10,11 +10,11 @@ using System.Collections.Immutable;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
using Xunit;

2
tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs

@ -14,10 +14,10 @@ using Microsoft.Extensions.Options;
using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Xunit;

2
tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs

@ -9,12 +9,12 @@ using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using FakeItEasy;
using Squidex.Domain.Apps.Core.Rules.Actions;
using Squidex.Domain.Apps.Core.Rules.Triggers;
using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Domain.Apps.Entities.TestHelpers;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Domain.Apps.Rules.Action.Webhook;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Log;

1
tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj

@ -14,6 +14,7 @@
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Entities.MongoDb\Squidex.Domain.Apps.Entities.MongoDb.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Rules\Squidex.Domain.Apps.Rules.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" />
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />

Loading…
Cancel
Save