Browse Source

Feature/full delete (#765)

* Started with a few repositories.

* Get rid unnecessary allocations.

* New repositories and cancellation token improvements.

* First untested implementation.

* Fix infrastructure tests.

* Warnings fixed.

* All tests green again.

* New tests.

* Better cancellation.

* More tests.

* Backup fix.

* Markdown everything

* A few more tests and fixes.

* Fix app deletion flag.

* Provide app to client creator.

* Started with migration.

* Temp.

* Minor change to rename app deletion.

* Toggle for deleter.

* Fix build.
pull/768/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
4d9fbd7322
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 69
      backend/.editorconfig
  2. 2
      backend/extensions/Squidex.Extensions/APM/ApplicationInsights/ApplicationInsightsPlugin.cs
  3. 2
      backend/extensions/Squidex.Extensions/APM/Otlp/OtlpPlugin.cs
  4. 2
      backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverExceptionHandler.cs
  5. 2
      backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverPlugin.cs
  6. 3
      backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs
  7. 5
      backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs
  8. 3
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs
  9. 3
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  10. 3
      backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  11. 3
      backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  12. 3
      backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs
  13. 3
      backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs
  14. 5
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs
  15. 8
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  16. 3
      backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs
  17. 3
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  18. 3
      backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs
  19. 3
      backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs
  20. 3
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs
  21. 3
      backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs
  22. 3
      backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs
  23. 3
      backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs
  24. 6
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs
  25. 2
      backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs
  26. 2
      backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs
  27. 4
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  28. 12
      backend/i18n/frontend_en.json
  29. 10
      backend/i18n/frontend_it.json
  30. 10
      backend/i18n/frontend_nl.json
  31. 10
      backend/i18n/frontend_zh.json
  32. 12
      backend/i18n/source/frontend_en.json
  33. 5
      backend/i18n/source/frontend_it.json
  34. 5
      backend/i18n/source/frontend_nl.json
  35. 5
      backend/i18n/source/frontend_zh.json
  36. 23
      backend/src/Migrations/MigrationPath.cs
  37. 4
      backend/src/Migrations/Migrations.csproj
  38. 3
      backend/src/Migrations/Migrations/ClearRules.cs
  39. 3
      backend/src/Migrations/Migrations/ClearSchemas.cs
  40. 3
      backend/src/Migrations/Migrations/ConvertEventStore.cs
  41. 3
      backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs
  42. 9
      backend/src/Migrations/Migrations/CreateAssetSlugs.cs
  43. 20
      backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs
  44. 30
      backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs
  45. 5
      backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs
  46. 3
      backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs
  47. 3
      backend/src/Migrations/Migrations/MongoDb/DeleteContentCollections.cs
  48. 3
      backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs
  49. 3
      backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs
  50. 7
      backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs
  51. 189
      backend/src/Migrations/Migrations/PopulateGrainIndexes.cs
  52. 3
      backend/src/Migrations/Migrations/RebuildApps.cs
  53. 3
      backend/src/Migrations/Migrations/RebuildAssetFolders.cs
  54. 3
      backend/src/Migrations/Migrations/RebuildAssets.cs
  55. 3
      backend/src/Migrations/Migrations/RebuildContents.cs
  56. 34
      backend/src/Migrations/Migrations/RebuildRules.cs
  57. 34
      backend/src/Migrations/Migrations/RebuildSchemas.cs
  58. 3
      backend/src/Migrations/Migrations/RebuildSnapshots.cs
  59. 3
      backend/src/Migrations/Migrations/StartEventConsumers.cs
  60. 3
      backend/src/Migrations/Migrations/StopEventConsumers.cs
  61. 24
      backend/src/Migrations/OldEvents/AppArchived.cs
  62. 5
      backend/src/Migrations/OldEvents/AppClientChanged.cs
  63. 14
      backend/src/Migrations/RebuildRunner.cs
  64. 18
      backend/src/Migrations/RebuilderExtensions.cs
  65. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs
  66. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
  67. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Partitioning.cs
  68. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs
  69. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  70. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  71. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj
  72. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs
  73. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs
  74. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleService.cs
  75. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs
  76. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Result.cs
  77. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  78. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  79. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  80. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  81. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  82. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  83. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  84. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs
  85. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs
  86. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  87. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/DateTimeFluidExtension.cs
  88. 13
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs
  89. 46
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  90. 86
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  91. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  92. 43
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs
  93. 5
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  94. 43
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  95. 44
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  96. 15
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  97. 74
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  98. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs
  99. 9
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs
  100. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs

69
backend/.editorconfig

@ -34,6 +34,75 @@ dotnet_diagnostic.IDE0066.severity = none
# IDE0090: Use 'new(...)'
dotnet_diagnostic.IDE0090.severity = none
# MA0002: IEqualityComparer<string> or IComparer<string> is missing
dotnet_diagnostic.MA0002.severity = none
# MA0003: Add argument name to improve readability
dotnet_diagnostic.MA0003.severity = none
# MA0004: Use Task.ConfigureAwait(false)
dotnet_diagnostic.MA0004.severity = none
# MA0006: Use String.Equals instead of equality operator
dotnet_diagnostic.MA0006.severity = none
# MA0007: Add a comma after the last value
dotnet_diagnostic.MA0007.severity = none
# MA0008: Add StructLayoutAttribute
dotnet_diagnostic.MA0008.severity = none
# MA0009: Add regex evaluation timeout
dotnet_diagnostic.MA0009.severity = none
# MA0016: Prefer return collection abstraction instead of implementation
dotnet_diagnostic.MA0016.severity = none
# MA0018: Do not declare static members on generic types
dotnet_diagnostic.MA0018.severity = none
# MA0025: Implement the functionality instead of throwing NotImplementedException
dotnet_diagnostic.MA0025.severity = none
# MA0028: Optimize StringBuilder usage
dotnet_diagnostic.MA0028.severity = none
# MA0029: Combine LINQ methods
dotnet_diagnostic.MA0029.severity = none
# MA0031: Optimize Enumerable.Count() usage
dotnet_diagnostic.MA0031.severity = none
# MA0036: Make class static
dotnet_diagnostic.MA0036.severity = none
# MA0038: Make method static
dotnet_diagnostic.MA0038.severity = none
# MA0039: Do not write your own certificate validation method
dotnet_diagnostic.MA0039.severity = none
# MA0048: File name must match type name
dotnet_diagnostic.MA0048.severity = none
# MA0049: Type name should not match containing namespace
dotnet_diagnostic.MA0049.severity = none
# MA0051: Method is too long
dotnet_diagnostic.MA0051.severity = none
# MA0069: Non-constant static fields should not be visible
dotnet_diagnostic.MA0069.severity = none
# MA0071: Avoid using redundant else
dotnet_diagnostic.MA0071.severity = none
# MA0076: Do not use implicit culture-sensitive ToString in interpolated strings
dotnet_diagnostic.MA0076.severity = none
# MA0097: A class that implements IComparable<T> or IComparable should override comparison operators
dotnet_diagnostic.MA0097.severity = none
# RECS0129: Removes 'internal' modifiers that are not required
dotnet_diagnostic.RECS0129.severity = none

2
backend/extensions/Squidex.Extensions/APM/ApplicationInsights/ApplicationInsightsPlugin.cs

@ -16,7 +16,7 @@ namespace Squidex.Extensions.APM.ApplicationInsights
{
public sealed class ApplicationInsightsPlugin : IPlugin
{
private class Configurator : ITelemetryConfigurator
private sealed class Configurator : ITelemetryConfigurator
{
private readonly IConfiguration config;

2
backend/extensions/Squidex.Extensions/APM/Otlp/OtlpPlugin.cs

@ -16,7 +16,7 @@ namespace Squidex.Extensions.APM.Datadog
{
public sealed class OtlpPlugin : IPlugin
{
private class Configurator : ITelemetryConfigurator
private sealed class Configurator : ITelemetryConfigurator
{
private readonly IConfiguration config;

2
backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverExceptionHandler.cs

@ -30,7 +30,7 @@ namespace Squidex.Extensions.APM.Stackdriver
public string GetHttpMethod()
{
return httpContextAccessor.HttpContext?.Request?.Method?.ToString() ?? string.Empty;
return httpContextAccessor.HttpContext?.Request?.Method ?? string.Empty;
}
public string GetUri()

2
backend/extensions/Squidex.Extensions/APM/Stackdriver/StackdriverPlugin.cs

@ -18,7 +18,7 @@ namespace Squidex.Extensions.APM.Stackdriver
{
public sealed class StackdriverPlugin : IPlugin
{
private class Configurator : ITelemetryConfigurator
private sealed class Configurator : ITelemetryConfigurator
{
private readonly string projectId;

3
backend/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs

@ -94,7 +94,8 @@ namespace Squidex.Extensions.Actions.Algolia
return ("Ignore", new AlgoliaJob());
}
protected override async Task<Result> ExecuteJobAsync(AlgoliaJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(AlgoliaJob job,
CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(job.AppId))
{

5
backend/extensions/Squidex.Extensions/Actions/AzureQueue/AzureQueueActionHandler.cs

@ -1,4 +1,4 @@
// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -58,7 +58,8 @@ namespace Squidex.Extensions.Actions.AzureQueue
return (ruleDescription, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(AzureQueueJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(AzureQueueJob job,
CancellationToken ct = default)
{
var queue = await clients.GetClientAsync((job.QueueConnectionString, job.QueueName));

3
backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs

@ -54,7 +54,8 @@ namespace Squidex.Extensions.Actions.Comment
return ("Ignore", new CreateComment());
}
protected override async Task<Result> ExecuteJobAsync(CreateComment job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(CreateComment job,
CancellationToken ct = default)
{
if (job.CommentsId == default)
{

3
backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs

@ -72,7 +72,8 @@ namespace Squidex.Extensions.Actions.CreateContent
return (Description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(Command job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(Command job,
CancellationToken ct = default)
{
var command = job;

3
backend/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs

@ -65,7 +65,8 @@ namespace Squidex.Extensions.Actions.Discourse
return (description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(DiscourseJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(DiscourseJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

3
backend/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -100,7 +100,8 @@ namespace Squidex.Extensions.Actions.ElasticSearch
return ("Ignore", new ElasticSearchJob());
}
protected override async Task<Result> ExecuteJobAsync(ElasticSearchJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(ElasticSearchJob job,
CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(job.ServerHost))
{

3
backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs

@ -41,7 +41,8 @@ namespace Squidex.Extensions.Actions.Email
return (description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(EmailJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(EmailJob job,
CancellationToken ct = default)
{
using (var smtpClient = new SmtpClient())
{

3
backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs

@ -46,7 +46,8 @@ namespace Squidex.Extensions.Actions.Fastly
return (Description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(FastlyJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(FastlyJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

5
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaActionHandler.cs

@ -74,7 +74,7 @@ namespace Squidex.Extensions.Actions.Kafka
foreach (var line in lines)
{
var indexEqual = line.IndexOf('=');
var indexEqual = line.IndexOf('=', StringComparison.Ordinal);
if (indexEqual > 0 && indexEqual < line.Length - 1)
{
@ -90,7 +90,8 @@ namespace Squidex.Extensions.Actions.Kafka
return headersDictionary;
}
protected override async Task<Result> ExecuteJobAsync(KafkaJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(KafkaJob job,
CancellationToken ct = default)
{
try
{

8
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -112,7 +112,8 @@ namespace Squidex.Extensions.Actions.Kafka
.WriteProperty("reason", error.Reason));
}
public async Task SendAsync(KafkaJob job, CancellationToken ct)
public async Task SendAsync(KafkaJob job,
CancellationToken ct)
{
if (!string.IsNullOrWhiteSpace(job.Schema))
{
@ -130,7 +131,8 @@ namespace Squidex.Extensions.Actions.Kafka
}
}
private static async Task ProduceAsync<T>(IProducer<string, T> producer, Message<string, T> message, KafkaJob job, CancellationToken ct)
private static async Task ProduceAsync<T>(IProducer<string, T> producer, Message<string, T> message, KafkaJob job,
CancellationToken ct)
{
message.Key = job.MessageKey;
@ -146,7 +148,7 @@ namespace Squidex.Extensions.Actions.Kafka
if (!string.IsNullOrWhiteSpace(job.PartitionKey) && job.PartitionCount > 0)
{
var partition = Math.Abs(job.PartitionKey.GetHashCode()) % job.PartitionCount;
var partition = Math.Abs(job.PartitionKey.GetHashCode(StringComparison.Ordinal)) % job.PartitionCount;
await producer.ProduceAsync(new TopicPartition(job.TopicName, partition), message, ct);
}

3
backend/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs

@ -79,7 +79,8 @@ namespace Squidex.Extensions.Actions.Medium
}
}
protected override async Task<Result> ExecuteJobAsync(MediumJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(MediumJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

3
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs

@ -72,7 +72,8 @@ namespace Squidex.Extensions.Actions.Notification
return ("Ignore", new CreateComment());
}
protected override async Task<Result> ExecuteJobAsync(CreateComment job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(CreateComment job,
CancellationToken ct = default)
{
if (job.CommentsId == default)
{

3
backend/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs

@ -34,7 +34,8 @@ namespace Squidex.Extensions.Actions.Prerender
return ($"Recache {url}", new PrerenderJob { RequestBody = requestBody });
}
protected override async Task<Result> ExecuteJobAsync(PrerenderJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(PrerenderJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

3
backend/extensions/Squidex.Extensions/Actions/RuleHelper.cs

@ -45,7 +45,8 @@ namespace Squidex.Extensions.Actions
return @event is EnrichedAssetEvent { Type: EnrichedAssetEventType.Deleted };
}
public static async Task<Result> OneWayRequestAsync(this HttpClient client, HttpRequestMessage request, string requestBody = null, CancellationToken ct = default)
public static async Task<Result> OneWayRequestAsync(this HttpClient client, HttpRequestMessage request, string requestBody = null,
CancellationToken ct = default)
{
HttpResponseMessage response = null;
try

3
backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs

@ -30,7 +30,8 @@ namespace Squidex.Extensions.Actions.Script
return Task.FromResult(($"Run a script", job));
}
protected override async Task<Result> ExecuteJobAsync(ScriptJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(ScriptJob job,
CancellationToken ct = default)
{
var vars = new ScriptVars
{

3
backend/extensions/Squidex.Extensions/Actions/SignalR/SignalRActionHandler.cs

@ -67,7 +67,8 @@ namespace Squidex.Extensions.Actions.SignalR
return (ruleDescription, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(SignalRJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(SignalRJob job,
CancellationToken ct = default)
{
var signalR = await clients.GetClientAsync((job.ConnectionString, job.HubName));

3
backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs

@ -40,7 +40,8 @@ namespace Squidex.Extensions.Actions.Slack
return (Description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(SlackJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(SlackJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

3
backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs

@ -39,7 +39,8 @@ namespace Squidex.Extensions.Actions.Twitter
return (Description, ruleJob);
}
protected override async Task<Result> ExecuteJobAsync(TweetJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(TweetJob job,
CancellationToken ct = default)
{
var tokens = Tokens.Create(
twitterOptions.ClientId,

6
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
@ -73,7 +74,7 @@ namespace Squidex.Extensions.Actions.Webhook
foreach (var line in lines)
{
var indexEqual = line.IndexOf('=');
var indexEqual = line.IndexOf('=', StringComparison.Ordinal);
if (indexEqual > 0 && indexEqual < line.Length - 1)
{
@ -89,7 +90,8 @@ namespace Squidex.Extensions.Actions.Webhook
return headersDictionary;
}
protected override async Task<Result> ExecuteJobAsync(WebhookJob job, CancellationToken ct = default)
protected override async Task<Result> ExecuteJobAsync(WebhookJob job,
CancellationToken ct = default)
{
using (var httpClient = httpClientFactory.CreateClient())
{

2
backend/extensions/Squidex.Extensions/Assets/Azure/AzureMetadataSource.cs

@ -56,7 +56,7 @@ namespace Squidex.Extensions.Assets.Azure
{
if (command.Type == AssetType.Image && command.File.FileSize <= MaxSize)
{
using (var stream = command.File.OpenRead())
await using (var stream = command.File.OpenRead())
{
var result = await client.AnalyzeImageInStreamAsync(stream, features);

2
backend/extensions/Squidex.Extensions/Samples/AssetStore/MemoryAssetStorePlugin.cs

@ -24,7 +24,7 @@ namespace Squidex.Extensions.Samples.AssetStore
{
builder.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/api/assets/memory"))
if (context.Request.Path.StartsWithSegments("/api/assets/memory", StringComparison.Ordinal))
{
context.Response.StatusCode = 200;

4
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -16,6 +16,10 @@
<PackageReference Include="Elasticsearch.Net" Version="7.14.1" />
<PackageReference Include="Google.Cloud.Diagnostics.Common" Version="4.0.0" />
<PackageReference Include="Google.Cloud.Logging.V2" Version="3.3.0" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.670">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.ComputerVision" Version="7.0.0" />
<PackageReference Include="Microsoft.Azure.SignalR.Management" Version="1.9.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />

12
backend/i18n/frontend_en.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "The app name cannot be changed later.",
"apps.appsButtonCreate": "Apps Overview",
"apps.appsButtonFallbackTitle": "Apps Overview",
"apps.archive": "Archive App",
"apps.archiveConfirmText": "Do you really want to archive this app?",
"apps.archiveConfirmTitle": "Archive App",
"apps.archiveFailed": "Failed to archive app. Please reload.",
"apps.archiveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.create": "Create App",
"apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.",
@ -26,9 +21,14 @@
"apps.createProfileApp": "New Profile Sample",
"apps.createProfileAppDescription": "Create your profile page.",
"apps.createWithTemplate": "Create {template} Sample",
"apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "You are not collaborating on any apps yet",
"apps.generalSettings": "General",
"apps.generalSettingsDangerZone": "General",
"apps.generalSettingsDangerZone": "Danger Zone",
"apps.image": "Image",
"apps.imageDrop": "Drop to upload",
"apps.leave": "Leave app",

10
backend/i18n/frontend_it.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "Il nome della app non potrà essere cambiato in un secondo momento.",
"apps.appsButtonCreate": "Nuova App",
"apps.appsButtonFallbackTitle": "Lista App",
"apps.archive": "Archivia l'App",
"apps.archiveConfirmText": "Rimuovi il pattern",
"apps.archiveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archiveFailed": "Non è stato possibile archiviare l'app. Per favore ricarica.",
"apps.archiveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",
@ -26,6 +21,11 @@
"apps.createProfileApp": "Nuovo Profilo",
"apps.createProfileAppDescription": "Crea la tua pagina del profilo.",
"apps.createWithTemplate": "Create un esempio di {template}",
"apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "Non stai ancora collaborando su nessuna app",
"apps.generalSettings": "Generale",
"apps.generalSettingsDangerZone": "Generale",

10
backend/i18n/frontend_nl.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "De app-naam kan later niet worden gewijzigd.",
"apps.appsButtonCreate": "Apps-overzicht",
"apps.appsButtonFallbackTitle": "Apps-overzicht",
"apps.archive": "App archiveren",
"apps.archiveConfirmText": "Patroon verwijderen",
"apps.archiveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archiveFailed": "Kan app niet archiveren. Laad opnieuw.",
"apps.archiveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",
@ -26,6 +21,11 @@
"apps.createProfileApp": "Nieuw profielvoorbeeld",
"apps.createProfileAppDescription": "Maak uw profielpagina.",
"apps.createWithTemplate": "Maak {sjabloon} voorbeeld",
"apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "Je werkt nog niet samen aan een app",
"apps.generalSettings": "Algemeen",
"apps.generalSettingsDangerZone": "Algemeen",

10
backend/i18n/frontend_zh.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "以后不能更改应用名称。",
"apps.appsButtonCreate": "应用概览",
"apps.appsButtonFallbackTitle": "应用概览",
"apps.archive": "存档应用",
"apps.archiveConfirmText": "你真的要存档这个应用程序吗?",
"apps.archiveConfirmTitle": "存档应用程序",
"apps.archiveFailed": "存档应用失败。请重新加载。",
"apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。",
"apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",
@ -26,6 +21,11 @@
"apps.createProfileApp": "新配置文件示例",
"apps.createProfileAppDescription": "创建您的个人资料页面。",
"apps.createWithTemplate": "创建 {template} 示例",
"apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "您还没有与任何应用协作",
"apps.generalSettings": "通用",
"apps.generalSettingsDangerZone": "通用",

12
backend/i18n/source/frontend_en.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "The app name cannot be changed later.",
"apps.appsButtonCreate": "Apps Overview",
"apps.appsButtonFallbackTitle": "Apps Overview",
"apps.archive": "Archive App",
"apps.archiveConfirmText": "Do you really want to archive this app?",
"apps.archiveConfirmTitle": "Archive App",
"apps.archiveFailed": "Failed to archive app. Please reload.",
"apps.archiveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.create": "Create App",
"apps.createBlankApp": "New App",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.",
@ -26,9 +21,14 @@
"apps.createProfileApp": "New Profile Sample",
"apps.createProfileAppDescription": "Create your profile page.",
"apps.createWithTemplate": "Create {template} Sample",
"apps.delete": "Delete App",
"apps.deleteConfirmText": "Do you really want to delete this app?",
"apps.deleteConfirmTitle": "I understand. Delete my App",
"apps.deleteFailed": "Failed to delete app. Please reload.",
"apps.deleteWarning": "Once you delete an app, there is no going back. All your data will be deleted in the background.",
"apps.empty": "You are not collaborating on any apps yet",
"apps.generalSettings": "General",
"apps.generalSettingsDangerZone": "General",
"apps.generalSettingsDangerZone": "Danger Zone",
"apps.image": "Image",
"apps.imageDrop": "Drop to upload",
"apps.leave": "Leave app",

5
backend/i18n/source/frontend_it.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "Il nome della app non potrà essere cambiato in un secondo momento.",
"apps.appsButtonCreate": "Nuova App",
"apps.appsButtonFallbackTitle": "Lista App",
"apps.archive": "Archivia l'App",
"apps.archiveConfirmText": "Rimuovi il pattern",
"apps.archiveConfirmTitle": "Sei sicuro di voler archiviare questa app?",
"apps.archiveFailed": "Non è stato possibile archiviare l'app. Per favore ricarica.",
"apps.archiveWarning": "Una volta archiviata una App, non è possibile tornare indietro. Sii certo.",
"apps.create": "Crea un'App",
"apps.createBlankApp": "Nuova App.",
"apps.createBlankAppDescription": "Crea una app vuota senza contenuti o schema.",

5
backend/i18n/source/frontend_nl.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "De app-naam kan later niet worden gewijzigd.",
"apps.appsButtonCreate": "Apps-overzicht",
"apps.appsButtonFallbackTitle": "Apps-overzicht",
"apps.archive": "App archiveren",
"apps.archiveConfirmText": "Patroon verwijderen",
"apps.archiveConfirmTitle": "Wil je deze app echt archiveren?",
"apps.archiveFailed": "Kan app niet archiveren. Laad opnieuw.",
"apps.archiveWarning": "Zodra je een app archiveert, is er geen weg meer terug. Wees alsjeblieft zeker.",
"apps.create": "App maken",
"apps.createBlankApp": "Nieuwe app.",
"apps.createBlankAppDescription": "Maak een nieuwe lege app zonder inhoud en schema's.",

5
backend/i18n/source/frontend_zh.json

@ -12,11 +12,6 @@
"apps.appNameWarning": "以后不能更改应用名称。",
"apps.appsButtonCreate": "应用概览",
"apps.appsButtonFallbackTitle": "应用概览",
"apps.archive": "存档应用",
"apps.archiveConfirmText": "你真的要存档这个应用程序吗?",
"apps.archiveConfirmTitle": "存档应用程序",
"apps.archiveFailed": "存档应用失败。请重新加载。",
"apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。",
"apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",

23
backend/src/Migrations/MigrationPath.cs

@ -18,7 +18,7 @@ namespace Migrations
{
public sealed class MigrationPath : IMigrationPath
{
private const int CurrentVersion = 25;
private const int CurrentVersion = 26;
private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider)
@ -75,17 +75,12 @@ namespace Migrations
// Version 12: Introduce roles.
// Version 24: Improve a naming in the languages config.
if (version < 24)
// Version 26: Introduce full deletion.
if (version < 26)
{
yield return serviceProvider.GetRequiredService<RebuildApps>();
}
// Version 14: Schema refactoring
// Version 22: Introduce domain id.
if (version < 22)
{
yield return serviceProvider.GetRequiredService<ClearSchemas>();
yield return serviceProvider.GetRequiredService<ClearRules>();
// yield return serviceProvider.GetRequiredService<RebuildApps>();
// yield return serviceProvider.GetRequiredService<RebuildSchemas>();
yield return serviceProvider.GetRequiredService<RebuildRules>();
}
// Version 18: Rebuild assets.
@ -130,12 +125,6 @@ namespace Migrations
yield return serviceProvider.GetRequiredService<ConvertRuleEventsJson>();
}
// Version 19: Unify indexes.
if (version < 19)
{
yield return serviceProvider.GetRequiredService<PopulateGrainIndexes>();
}
yield return serviceProvider.GetRequiredService<StartEventConsumers>();
}
}

4
backend/src/Migrations/Migrations.csproj

@ -5,6 +5,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="1.0.670">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
</ItemGroup>

3
backend/src/Migrations/Migrations/ClearRules.cs

@ -22,7 +22,8 @@ namespace Migrations.Migrations
this.store = store;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return store.ClearSnapshotsAsync();
}

3
backend/src/Migrations/Migrations/ClearSchemas.cs

@ -22,7 +22,8 @@ namespace Migrations.Migrations
this.store = store;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return store.ClearSnapshotsAsync();
}

3
backend/src/Migrations/Migrations/ConvertEventStore.cs

@ -24,7 +24,8 @@ namespace Migrations.Migrations
this.eventStore = eventStore;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
if (eventStore is MongoEventStore mongoEventStore)
{

3
backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs

@ -26,7 +26,8 @@ namespace Migrations.Migrations
this.eventStore = eventStore;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
if (eventStore is MongoEventStore mongoEventStore)
{

9
backend/src/Migrations/Migrations/CreateAssetSlugs.cs

@ -24,16 +24,17 @@ namespace Migrations.Migrations
this.stateForAssets = stateForAssets;
}
public Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
return stateForAssets.ReadAllAsync(async (state, version) =>
await foreach (var (state, version) in stateForAssets.ReadAllAsync(ct))
{
state.Slug = state.FileName.ToAssetSlug();
var key = DomainId.Combine(state.AppId.Id, state.Id);
await stateForAssets.WriteAsync(key, state, version, version);
}, ct);
await stateForAssets.WriteAsync(key, state, version, version, ct);
}
}
}
}

20
backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs

@ -15,6 +15,7 @@ using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Tasks;
namespace Migrations.Migrations.MongoDb
@ -28,13 +29,14 @@ namespace Migrations.Migrations.MongoDb
this.database = database;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
const int SizeOfBatch = 1000;
const int SizeOfQueue = 20;
var collectionOld = database.GetCollection<BsonDocument>("Events");
var collectionNew = database.GetCollection<BsonDocument>("Events2");
var collectionV1 = database.GetCollection<BsonDocument>("Events");
var collectionV2 = database.GetCollection<BsonDocument>("Events2");
var batchBlock = new BatchBlock<BsonDocument>(SizeOfBatch, new GroupingDataflowBlockOptions
{
@ -60,7 +62,7 @@ namespace Migrations.Migrations.MongoDb
{
if (!eventStream.StartsWith("app-", StringComparison.OrdinalIgnoreCase))
{
var indexOfType = eventStream.IndexOf('-');
var indexOfType = eventStream.IndexOf('-', StringComparison.Ordinal);
var indexOfId = indexOfType + 1;
var indexOfOldId = eventStream.LastIndexOf("--", StringComparison.OrdinalIgnoreCase);
@ -104,7 +106,7 @@ namespace Migrations.Migrations.MongoDb
if (writes.Count > 0)
{
await collectionNew.BulkWriteAsync(writes, writeOptions);
await collectionV2.BulkWriteAsync(writes, writeOptions, ct);
}
}
catch (OperationCanceledException ex)
@ -121,7 +123,13 @@ namespace Migrations.Migrations.MongoDb
batchBlock.BidirectionalLinkTo(actionBlock);
await collectionOld.Find(new BsonDocument()).ForEachAsync(batchBlock.SendAsync, ct);
await foreach (var commit in collectionV1.Find(new BsonDocument()).ToAsyncEnumerable(ct: ct))
{
if (!await batchBlock.SendAsync(commit, ct))
{
break;
}
}
batchBlock.Complete();

30
backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs

@ -57,7 +57,8 @@ namespace Migrations.Migrations.MongoDb
return this;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
switch (scope)
{
@ -72,25 +73,26 @@ namespace Migrations.Migrations.MongoDb
}
}
private static async Task RebuildAsync(IMongoDatabase database, Action<BsonDocument>? extraAction, string collectionNameOld, CancellationToken ct)
private static async Task RebuildAsync(IMongoDatabase database, Action<BsonDocument>? extraAction, string collectionNameV1,
CancellationToken ct)
{
const int SizeOfBatch = 1000;
const int SizeOfQueue = 10;
string collectionNameNew;
string collectionNameV2;
collectionNameNew = $"{collectionNameOld}2";
collectionNameNew = collectionNameNew.Replace("State_", "States_");
collectionNameV2 = $"{collectionNameV1}2";
collectionNameV2 = collectionNameV2.Replace("State_", "States_", StringComparison.Ordinal);
var collectionOld = database.GetCollection<BsonDocument>(collectionNameOld);
var collectionNew = database.GetCollection<BsonDocument>(collectionNameNew);
var collectionV1 = database.GetCollection<BsonDocument>(collectionNameV1);
var collectionV2 = database.GetCollection<BsonDocument>(collectionNameV2);
if (!await collectionOld.AnyAsync())
if (!await collectionV1.AnyAsync(ct: ct))
{
return;
}
await collectionNew.DeleteManyAsync(new BsonDocument(), ct);
await collectionV2.DeleteManyAsync(new BsonDocument(), ct);
var batchBlock = new BatchBlock<BsonDocument>(SizeOfBatch, new GroupingDataflowBlockOptions
{
@ -138,7 +140,7 @@ namespace Migrations.Migrations.MongoDb
if (writes.Count > 0)
{
await collectionNew.BulkWriteAsync(writes, writeOptions);
await collectionV2.BulkWriteAsync(writes, writeOptions, ct);
}
}
catch (OperationCanceledException ex)
@ -155,7 +157,13 @@ namespace Migrations.Migrations.MongoDb
batchBlock.BidirectionalLinkTo(actionBlock);
await collectionOld.Find(new BsonDocument()).ForEachAsync(batchBlock.SendAsync, ct);
await foreach (var document in collectionV1.Find(new BsonDocument()).ToAsyncEnumerable(ct: ct))
{
if (!await batchBlock.SendAsync(document, ct))
{
break;
}
}
batchBlock.Complete();

5
backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs

@ -23,7 +23,8 @@ namespace Migrations.Migrations.MongoDb
this.database = database;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
var collections = new[]
{
@ -39,7 +40,7 @@ namespace Migrations.Migrations.MongoDb
return Task.WhenAll(
collections
.Select(x => database.GetCollection<BsonDocument>(x))
.Select(x => x.UpdateManyAsync(filter, update)));
.Select(x => x.UpdateManyAsync(filter, update, cancellationToken: ct)));
}
}
}

3
backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs

@ -22,7 +22,8 @@ namespace Migrations.Migrations.MongoDb
collection = database.GetCollection<BsonDocument>("RuleEvents");
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
foreach (var document in collection.Find(new BsonDocument()).ToEnumerable(ct))
{

3
backend/src/Migrations/Migrations/MongoDb/DeleteContentCollections.cs

@ -21,7 +21,8 @@ namespace Migrations.Migrations.MongoDb
this.database = database;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
await database.DropCollectionAsync("States_Contents", ct);
await database.DropCollectionAsync("States_Contents_Archive", ct);

3
backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs

@ -22,7 +22,8 @@ namespace Migrations.Migrations.MongoDb
this.database = database;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
var collection = database.GetCollection<BsonDocument>("States_Assets");

3
backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs

@ -22,7 +22,8 @@ namespace Migrations.Migrations.MongoDb
this.database = database;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
var collection = database.GetCollection<BsonDocument>("States_Assets");

7
backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs

@ -23,16 +23,17 @@ namespace Migrations.Migrations.MongoDb
this.contentDatabase = contentDatabase;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
if (await contentDatabase.CollectionExistsAsync("State_Content_Draft"))
if (await contentDatabase.CollectionExistsAsync("State_Content_Draft", ct))
{
await contentDatabase.DropCollectionAsync("State_Contents", ct);
await contentDatabase.DropCollectionAsync("State_Content_Published", ct);
await contentDatabase.RenameCollectionAsync("State_Content_Draft", "State_Contents", cancellationToken: ct);
}
if (await contentDatabase.CollectionExistsAsync("State_Contents"))
if (await contentDatabase.CollectionExistsAsync("State_Contents", ct))
{
var collection = contentDatabase.GetCollection<BsonDocument>("State_Contents");

189
backend/src/Migrations/Migrations/PopulateGrainIndexes.cs

@ -1,189 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Rules.Indexes;
using Squidex.Domain.Apps.Entities.Schemas.Indexes;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations
{
public class PopulateGrainIndexes : IMigration
{
private readonly IAppsIndex indexApps;
private readonly IRulesIndex indexRules;
private readonly ISchemasIndex indexSchemas;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
public PopulateGrainIndexes(IAppsIndex indexApps, IRulesIndex indexRules, ISchemasIndex indexSchemas,
IEventDataFormatter eventDataFormatter,
IEventStore eventStore)
{
this.indexApps = indexApps;
this.indexRules = indexRules;
this.indexSchemas = indexSchemas;
this.eventDataFormatter = eventDataFormatter;
this.eventStore = eventStore;
}
public Task UpdateAsync(CancellationToken ct)
{
return Task.WhenAll(
RebuildAppIndexes(ct),
RebuildRuleIndexes(ct),
RebuildSchemaIndexes(ct));
}
private async Task RebuildAppIndexes(CancellationToken ct)
{
var appsByName = new Dictionary<string, DomainId>();
var appsByUser = new Dictionary<string, HashSet<DomainId>>();
bool HasApp(NamedId<DomainId> appId, bool consistent, out DomainId id)
{
return appsByName!.TryGetValue(appId.Name, out id) && (!consistent || id == appId.Id);
}
HashSet<DomainId> Index(string contributorId)
{
return appsByUser!.GetOrAddNew(contributorId);
}
void RemoveApp(NamedId<DomainId> appId, bool consistent)
{
if (HasApp(appId, consistent, out var id))
{
foreach (var apps in appsByUser!.Values)
{
apps.Remove(id);
}
appsByName!.Remove(appId.Name);
}
}
await foreach (var storedEvent in eventStore.QueryAllAsync("^app\\-", ct: ct))
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
if (@event != null)
{
switch (@event.Payload)
{
case AppCreated created:
{
RemoveApp(created.AppId, false);
appsByName[created.Name] = created.AppId.Id;
break;
}
case AppContributorAssigned contributorAssigned:
{
if (HasApp(contributorAssigned.AppId, true, out _))
{
Index(contributorAssigned.ContributorId).Add(contributorAssigned.AppId.Id);
}
break;
}
case AppContributorRemoved contributorRemoved:
Index(contributorRemoved.ContributorId).Remove(contributorRemoved.AppId.Id);
break;
case AppArchived archived:
RemoveApp(archived.AppId, true);
break;
}
}
}
await indexApps.RebuildAsync(appsByName);
foreach (var (contributorId, apps) in appsByUser)
{
await indexApps.RebuildByContributorsAsync(contributorId, apps);
}
}
private async Task RebuildRuleIndexes(CancellationToken ct)
{
var rulesByApp = new Dictionary<DomainId, HashSet<DomainId>>();
HashSet<DomainId> Index(RuleEvent @event)
{
return rulesByApp!.GetOrAddNew(@event.AppId.Id);
}
await foreach (var storedEvent in eventStore.QueryAllAsync("^rule\\-", ct: ct))
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
if (@event != null)
{
switch (@event.Payload)
{
case RuleCreated created:
Index(created).Add(created.RuleId);
break;
case RuleDeleted deleted:
Index(deleted).Remove(deleted.RuleId);
break;
}
}
}
foreach (var (appId, rules) in rulesByApp)
{
await indexRules.RebuildAsync(appId, rules);
}
}
private async Task RebuildSchemaIndexes(CancellationToken ct)
{
var schemasByApp = new Dictionary<DomainId, Dictionary<string, DomainId>>();
Dictionary<string, DomainId> Index(SchemaEvent @event)
{
return schemasByApp!.GetOrAddNew(@event.AppId.Id);
}
await foreach (var storedEvent in eventStore.QueryAllAsync("^schema\\-", ct: ct))
{
var @event = eventDataFormatter.ParseIfKnown(storedEvent);
if (@event != null)
{
switch (@event.Payload)
{
case SchemaCreated created:
Index(created)[created.SchemaId.Name] = created.SchemaId.Id;
break;
case SchemaDeleted deleted:
Index(deleted).Remove(deleted.SchemaId.Name);
break;
}
}
}
foreach (var (appId, schemas) in schemasByApp)
{
await indexSchemas.RebuildAsync(appId, schemas);
}
}
}
}

3
backend/src/Migrations/Migrations/RebuildApps.cs

@ -25,7 +25,8 @@ namespace Migrations.Migrations
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildAppsAsync(rebuildOptions.BatchSize, ct);
}

3
backend/src/Migrations/Migrations/RebuildAssetFolders.cs

@ -25,7 +25,8 @@ namespace Migrations.Migrations
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildAssetFoldersAsync(rebuildOptions.BatchSize, ct);
}

3
backend/src/Migrations/Migrations/RebuildAssets.cs

@ -25,7 +25,8 @@ namespace Migrations.Migrations
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildAssetsAsync(rebuildOptions.BatchSize, ct);
}

3
backend/src/Migrations/Migrations/RebuildContents.cs

@ -25,7 +25,8 @@ namespace Migrations.Migrations
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildContentAsync(rebuildOptions.BatchSize, ct);
}

34
backend/src/Migrations/Migrations/RebuildRules.cs

@ -0,0 +1,34 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations
{
public sealed class RebuildRules : IMigration
{
private readonly Rebuilder rebuilder;
private readonly RebuildOptions rebuildOptions;
public RebuildRules(Rebuilder rebuilder,
IOptions<RebuildOptions> rebuildOptions)
{
this.rebuilder = rebuilder;
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildRulesAsync(rebuildOptions.BatchSize, ct);
}
}
}

34
backend/src/Migrations/Migrations/RebuildSchemas.cs

@ -0,0 +1,34 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations
{
public sealed class RebuildSchemas : IMigration
{
private readonly Rebuilder rebuilder;
private readonly RebuildOptions rebuildOptions;
public RebuildSchemas(Rebuilder rebuilder,
IOptions<RebuildOptions> rebuildOptions)
{
this.rebuilder = rebuilder;
this.rebuildOptions = rebuildOptions.Value;
}
public Task UpdateAsync(
CancellationToken ct)
{
return rebuilder.RebuildSchemasAsync(rebuildOptions.BatchSize, ct);
}
}
}

3
backend/src/Migrations/Migrations/RebuildSnapshots.cs

@ -25,7 +25,8 @@ namespace Migrations.Migrations
this.rebuildOptions = rebuildOptions.Value;
}
public async Task UpdateAsync(CancellationToken ct)
public async Task UpdateAsync(
CancellationToken ct)
{
await rebuilder.RebuildAppsAsync(rebuildOptions.BatchSize, ct);
await rebuilder.RebuildSchemasAsync(rebuildOptions.BatchSize, ct);

3
backend/src/Migrations/Migrations/StartEventConsumers.cs

@ -23,7 +23,8 @@ namespace Migrations.Migrations
eventConsumerManager = grainFactory.GetGrain<IEventConsumerManagerGrain>(SingleGrain.Id);
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return eventConsumerManager.StartAllAsync();
}

3
backend/src/Migrations/Migrations/StopEventConsumers.cs

@ -23,7 +23,8 @@ namespace Migrations.Migrations
eventConsumerManager = grainFactory.GetGrain<IEventConsumerManagerGrain>(SingleGrain.Id);
}
public Task UpdateAsync(CancellationToken ct)
public Task UpdateAsync(
CancellationToken ct)
{
return eventConsumerManager.StopAllAsync();
}

24
backend/src/Migrations/OldEvents/AppArchived.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.Reflection;
namespace Migrations.OldEvents
{
[EventType(nameof(AppArchived))]
public sealed class AppArchived : AppEvent, IMigrated<IEvent>
{
public IEvent Migrate()
{
return SimpleMapper.Map(this, new AppDeleted());
}
}
}

5
backend/src/Migrations/OldEvents/AppClientChanged.cs

@ -23,7 +23,10 @@ namespace Migrations.OldEvents
public IEvent Migrate()
{
var permission = IsReader ? AppClientPermission.Reader : AppClientPermission.Editor;
var permission =
IsReader ?
AppClientPermission.Reader :
AppClientPermission.Editor;
return SimpleMapper.Map(this, new AppClientUpdated { Permission = permission });
}

14
backend/src/Migrations/RebuildRunner.cs

@ -8,7 +8,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Migrations.Migrations;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure.Commands;
@ -18,22 +17,20 @@ namespace Migrations
{
private readonly RebuildFiles rebuildFiles;
private readonly Rebuilder rebuilder;
private readonly PopulateGrainIndexes populateGrainIndexes;
private readonly RebuildOptions rebuildOptions;
public RebuildRunner(
IOptions<RebuildOptions> rebuildOptions,
Rebuilder rebuilder,
RebuildFiles rebuildFiles,
PopulateGrainIndexes populateGrainIndexes)
RebuildFiles rebuildFiles)
{
this.rebuildFiles = rebuildFiles;
this.rebuilder = rebuilder;
this.rebuildOptions = rebuildOptions.Value;
this.populateGrainIndexes = populateGrainIndexes;
}
public async Task RunAsync(CancellationToken ct)
public async Task RunAsync(
CancellationToken ct)
{
var batchSize = rebuildOptions.CalculateBatchSize();
@ -67,11 +64,6 @@ namespace Migrations
{
await rebuilder.RebuildContentAsync(batchSize, ct);
}
if (rebuildOptions.Indexes)
{
await populateGrainIndexes.UpdateAsync(ct);
}
}
}
}

18
backend/src/Migrations/RebuilderExtensions.cs

@ -18,32 +18,38 @@ namespace Migrations
{
public static class RebuilderExtensions
{
public static Task RebuildAppsAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildAppsAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<AppDomainObject, AppDomainObject.State>("^app\\-", batchSize, ct);
}
public static Task RebuildSchemasAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildSchemasAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<SchemaDomainObject, SchemaDomainObject.State>("^schema\\-", batchSize, ct);
}
public static Task RebuildRulesAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildRulesAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<RuleDomainObject, RuleDomainObject.State>("^rule\\-", batchSize, ct);
}
public static Task RebuildAssetsAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildAssetsAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<AssetDomainObject, AssetDomainObject.State>("^asset\\-", batchSize, ct);
}
public static Task RebuildAssetFoldersAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildAssetFoldersAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<AssetFolderDomainObject, AssetFolderDomainObject.State>("^assetFolder\\-", batchSize, ct);
}
public static Task RebuildContentAsync(this Rebuilder rebuilder, int batchSize, CancellationToken ct = default)
public static Task RebuildContentAsync(this Rebuilder rebuilder, int batchSize,
CancellationToken ct = default)
{
return rebuilder.RebuildAsync<ContentDomainObject, ContentDomainObject.State>("^content\\-", batchSize, ct);
}

2
backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Contents
stream.Position = 0;
geoJSON = serializer.Deserialize<GeoJSONObject>(stream, null, leaveOpen: true);
geoJSON = serializer.Deserialize<GeoJSONObject>(stream, null, true);
return GeoJsonParseResult.Success;
}

4
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs

@ -36,12 +36,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public bool Equals(Status other)
{
return string.Equals(Name, other.Name);
return string.Equals(Name, other.Name, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return Name.GetHashCode();
return Name.GetHashCode(StringComparison.Ordinal);
}
public override string ToString()

2
backend/src/Squidex.Domain.Apps.Core.Model/Partitioning.cs

@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core
public override int GetHashCode()
{
return Key.GetHashCode();
return Key.GetHashCode(StringComparison.Ordinal);
}
public override string ToString()

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedCommentEvent.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
[IgnoreDataMember]
public override long Partition
{
get => MentionedUser?.Id.GetHashCode() ?? 0;
get => MentionedUser?.Id.GetHashCode(StringComparison.Ordinal) ?? 0;
}
public bool ShouldSerializeMentionedUser()

2
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Core.Rules
[Pure]
public Rule Rename(string newName)
{
if (string.Equals(Name, newName))
if (string.Equals(Name, newName, StringComparison.Ordinal))
{
return this;
}

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
[Pure]
public Schema ChangeCategory(string? category)
{
if (string.Equals(Category, category))
if (string.Equals(Category, category, StringComparison.Ordinal))
{
return this;
}

4
backend/src/Squidex.Domain.Apps.Core.Model/Squidex.Domain.Apps.Core.Model.csproj

@ -9,6 +9,10 @@
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="1.0.670">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="5.0.0" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

5
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
@ -18,12 +19,12 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{
public static string EscapeEdmField(this string field)
{
return field.Replace("-", "_");
return field.Replace("-", "_", StringComparison.Ordinal);
}
public static string UnescapeEdmField(this string field)
{
return field.Replace("_", "-");
return field.Replace("_", "-", StringComparison.Ordinal);
}
public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory typeFactory,

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs

@ -21,6 +21,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
Task<(string Description, object Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Task<Result> ExecuteJobAsync(object data, CancellationToken ct = default);
Task<Result> ExecuteJobAsync(object data,
CancellationToken ct = default);
}
}

9
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleService.cs

@ -20,10 +20,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules
string GetName(AppEvent @event);
IAsyncEnumerable<JobResult> CreateSnapshotJobsAsync(RuleContext context, CancellationToken ct = default);
IAsyncEnumerable<JobResult> CreateSnapshotJobsAsync(RuleContext context,
CancellationToken ct = default);
IAsyncEnumerable<JobResult> CreateJobsAsync(Envelope<IEvent> @event, RuleContext context, CancellationToken ct = default);
IAsyncEnumerable<JobResult> CreateJobsAsync(Envelope<IEvent> @event, RuleContext context,
CancellationToken ct = default);
Task<(Result Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job);
Task<(Result Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job,
CancellationToken ct = default);
}
}

6
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs

@ -24,12 +24,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get => false;
}
IAsyncEnumerable<EnrichedEvent> CreateSnapshotEventsAsync(RuleContext context, CancellationToken ct)
IAsyncEnumerable<EnrichedEvent> CreateSnapshotEventsAsync(RuleContext context,
CancellationToken ct)
{
return AsyncEnumerable.Empty<EnrichedEvent>();
}
IAsyncEnumerable<EnrichedEvent> CreateEnrichedEventsAsync(Envelope<AppEvent> @event, RuleContext context, CancellationToken ct);
IAsyncEnumerable<EnrichedEvent> CreateEnrichedEventsAsync(Envelope<AppEvent> @event, RuleContext context,
CancellationToken ct);
string? GetName(AppEvent @event)
{

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Result.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Globalization;
using System.Text;
namespace Squidex.Domain.Apps.Core.HandleRules
@ -41,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
}
dumpBuilder.AppendLine();
dumpBuilder.AppendFormat("Elapsed {0}.", elapsed);
dumpBuilder.AppendFormat(CultureInfo.InvariantCulture, "Elapsed {0}.", elapsed);
dumpBuilder.AppendLine();
Dump = dumpBuilder.ToString();

8
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -61,7 +61,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return (description, data!);
}
async Task<Result> IRuleActionHandler.ExecuteJobAsync(object data, CancellationToken ct)
async Task<Result> IRuleActionHandler.ExecuteJobAsync(object data,
CancellationToken ct)
{
var typedData = (TData)data;
@ -70,7 +71,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules
protected virtual Task<(string Description, TData Data)> CreateJobAsync(EnrichedEvent @event, TAction action)
{
#pragma warning disable MA0042 // Do not use blocking calls in an async method
return Task.FromResult(CreateJob(@event, action));
#pragma warning restore MA0042 // Do not use blocking calls in an async method
}
protected virtual (string Description, TData Data) CreateJob(EnrichedEvent @event, TAction action)
@ -78,6 +81,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
throw new NotImplementedException();
}
protected abstract Task<Result> ExecuteJobAsync(TData job, CancellationToken ct = default);
protected abstract Task<Result> ExecuteJobAsync(TData job,
CancellationToken ct = default);
}
}

11
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@ -25,8 +26,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public class RuleEventFormatter
{
private const string GlobalFallback = "null";
private static readonly Regex RegexPatternOld = new Regex(@"^(?<FullPath>(?<Type>[^_]*)_(?<Path>[^\s]*))", RegexOptions.Compiled);
private static readonly Regex RegexPatternNew = new Regex(@"^\{(?<FullPath>(?<Type>[\w]+)_(?<Path>[\w\.\-]+))[\s]*(\|[\s]*(?<Transform>[^\?}]+))?(\?[\s]*(?<Fallback>[^\}\s]+))?[\s]*\}", RegexOptions.Compiled);
private static readonly Regex RegexPatternOld = new Regex(@"^(?<FullPath>(?<Type>[^_]*)_(?<Path>[^\s]*))", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private static readonly Regex RegexPatternNew = new Regex(@"^\{(?<FullPath>(?<Type>[\w]+)_(?<Path>[\w\.\-]+))[\s]*(\|[\s]*(?<Transform>[^\?}]+))?(\?[\s]*(?<Fallback>[^\}\s]+))?[\s]*\}", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
private readonly IJsonSerializer jsonSerializer;
private readonly IEnumerable<IRuleEventFormatter> formatters;
private readonly ITemplateEngine templateEngine;
@ -111,7 +112,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules
["event"] = @event
};
#pragma warning disable MA0042 // Do not use blocking calls in an async method
var result = scriptEngine.Execute(vars, script).ToString();
#pragma warning restore MA0042 // Do not use blocking calls in an async method
if (result == "undefined")
{
@ -285,7 +288,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
if (instant.Success)
{
text = instant.Value.ToUnixTimeMilliseconds().ToString();
text = instant.Value.ToUnixTimeMilliseconds().ToString(CultureInfo.InvariantCulture);
}
break;
@ -297,7 +300,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
if (instant.Success)
{
text = instant.Value.ToUnixTimeSeconds().ToString();
text = instant.Value.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture);
}
break;

8
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -364,7 +364,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return @event.GetType().Name;
}
public async Task<(Result Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job)
public async Task<(Result Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job,
CancellationToken ct = default)
{
var actionWatch = ValueStopwatch.StartNew();
@ -379,7 +380,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules
using (var cts = new CancellationTokenSource(GetTimeoutInMs()))
{
result = await actionHandler.ExecuteJobAsync(deserialized, cts.Token).WithCancellation(cts.Token);
using (var combined = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ct))
{
result = await actionHandler.ExecuteJobAsync(deserialized, combined.Token).WithCancellation(combined.Token);
}
}
}
catch (Exception ex)

3
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Globalization;
using Jint;
using Jint.Native;
using Jint.Native.Object;
@ -107,7 +108,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
for (var i = 0; i < arr.Length; i++)
{
result.Add(Map(arr.Get(i.ToString())));
result.Add(Map(arr.Get(i.ToString(CultureInfo.InvariantCulture))));
}
return result;

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs

@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
private static async Task<JsValue> ParseResponse(ExecutionContext context, HttpResponseMessage response)
{
var responseString = await response.Content.ReadAsStringAsync();
var responseString = await response.Content.ReadAsStringAsync(context.CancellationToken);
context.CancellationToken.ThrowIfCancellationRequested();

6
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs

@ -14,9 +14,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
public interface IScriptEngine
{
Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default, CancellationToken ct = default);
Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default);
Task<ContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default, CancellationToken ct = default);
Task<ContentData> TransformAsync(ScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default);
IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default);

4
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
var tcs = new TaskCompletionSource<IJsonValue>();
using (combined.Token.Register(() => tcs.TrySetCanceled()))
await using (combined.Token.Register(() => tcs.TrySetCanceled(combined.Token)))
{
var context =
CreateEngine(options)
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
{
var tcs = new TaskCompletionSource<ContentData>();
using (combined.Token.Register(() => tcs.TrySetCanceled()))
await using (combined.Token.Register(() => tcs.TrySetCanceled(combined.Token)))
{
var context =
CreateEngine(options)

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
private static JsValue CreateUser(Engine engine, string id, bool isClient, string email, string? name, IEnumerable<Claim> allClaims)
{
var claims =
allClaims.GroupBy(x => x.Type.Split(ClaimSeparators).Last())
allClaims.GroupBy(x => x.Type.Split(ClaimSeparators)[^1])
.ToDictionary(
x => x.Key,
x => x.Select(y => y.Value).ToArray());

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOptions.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
public bool AsContext { get; set; }
public override string ToString()
public override readonly string ToString()
{
return $"CanReject={CanReject}, CanDisallow={CanDisallow}, AsContext={AsContext}";
}

4
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -18,6 +18,10 @@
<ItemGroup>
<PackageReference Include="Fluid.Core.Squidex" Version="1.0.0-beta" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.670">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.9.0" />
<PackageReference Include="NJsonSchema" Version="10.5.2" />

3
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/DateTimeFluidExtension.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Globalization;
using Fluid;
using Fluid.Values;
using NodaTime;
@ -86,7 +87,7 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
private static FluidValue Format(FilterArguments arguments, DateTimeOffset value)
{
var formatted = value.ToString(arguments.At(0).ToStringValue());
var formatted = value.ToString(arguments.At(0).ToStringValue(), CultureInfo.InvariantCulture);
return new StringValue(formatted);
}

13
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs

@ -1,4 +1,4 @@
// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -19,13 +19,20 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
private readonly Regex regex;
private readonly string? errorMessage;
public PatternValidator(string pattern, string? errorMessage = null)
public PatternValidator(string pattern, string? errorMessage = null, bool capture = false)
{
Guard.NotNullOrEmpty(pattern, nameof(pattern));
this.errorMessage = errorMessage;
regex = new Regex($"^{pattern}$", RegexOptions.None, Timeout);
var options = RegexOptions.None;
if (!capture)
{
options |= RegexOptions.ExplicitCapture;
}
regex = new Regex($"^{pattern}$", options, Timeout);
}
public Task ValidateAsync(object? value, ValidationContext context, AddError addError)

46
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs

@ -0,0 +1,46 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoState<AppDomainObject.State>
{
[BsonRequired]
[BsonElement("_an")]
public string IndexedName { get; set; }
[BsonRequired]
[BsonElement("_ui")]
public string[] IndexedUserIds { get; set; }
[BsonRequired]
[BsonElement("_dl")]
public bool IndexedDeleted { get; set; }
public override void Prepare()
{
var users = new HashSet<string>
{
Document.CreatedBy.Identifier
};
users.AddRange(Document.Contributors.Keys);
users.AddRange(Document.Clients.Keys);
IndexedUserIds = users.ToArray();
IndexedDeleted = Document.IsDeleted;
IndexedName = Document.Name;
}
}
}

86
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs

@ -0,0 +1,86 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.DomainObject;
using Squidex.Domain.Apps.Entities.Apps.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Apps
{
public sealed class MongoAppRepository : MongoSnapshotStoreBase<AppDomainObject.State, MongoAppEntity>, IAppRepository, IDeleter
{
public MongoAppRepository(IMongoDatabase database, JsonSerializer jsonSerializer)
: base(database, jsonSerializer)
{
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection,
CancellationToken ct)
{
return collection.Indexes.CreateManyAsync(new[]
{
new CreateIndexModel<MongoAppEntity>(
Index
.Ascending(x => x.IndexedName)),
new CreateIndexModel<MongoAppEntity>(
Index
.Ascending(x => x.IndexedUserIds))
}, ct);
}
Task IDeleter.DeleteAppAsync(IAppEntity app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.DocumentId, app.Id), ct);
}
public async Task<Dictionary<string, DomainId>> QueryIdsAsync(string contributorId,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryIdsAsync"))
{
var find = Collection.Find(x => x.IndexedUserIds.Contains(contributorId) && !x.IndexedDeleted);
return await QueryAsync(find, ct);
}
}
public async Task<Dictionary<string, DomainId>> QueryIdsAsync(IEnumerable<string> names,
CancellationToken ct = default)
{
using (Telemetry.Activities.StartActivity("MongoAppRepository/QueryAsync"))
{
var find = Collection.Find(x => names.Contains(x.IndexedName) && !x.IndexedDeleted);
return await QueryAsync(find, ct);
}
}
private static async Task<Dictionary<string, DomainId>> QueryAsync(IFindFluent<MongoAppEntity, MongoAppEntity> find,
CancellationToken ct)
{
var entities = await find.Only(x => x.DocumentId, x => x.IndexedName).ToListAsync(ct);
return entities.Select(x =>
{
var indexedId = DomainId.Create(x["_id"].AsString);
var indexedName = x["_an"].AsString;
return new { indexedName, indexedId };
}).ToDictionary(x => x.indexedName, x => x.indexedId);
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetFolderEntity> collection,
CancellationToken ct = default)
CancellationToken ct)
{
return collection.Indexes.CreateManyAsync(new[]
{

43
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
@ -20,15 +20,28 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderDomainObject.State>
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderDomainObject.State>, IDeleter
{
async Task<(AssetFolderDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<AssetFolderDomainObject.State>.ReadAsync(DomainId key)
Task IDeleter.DeleteAppAsync(IAppEntity app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, app.Id), ct);
}
IAsyncEnumerable<(AssetFolderDomainObject.State State, long Version)> ISnapshotStore<AssetFolderDomainObject.State>.ReadAllAsync(
CancellationToken ct)
{
return Collection.Find(new BsonDocument(), Batching.Options).ToAsyncEnumerable(ct).Select(x => (Map(x), x.Version));
}
async Task<(AssetFolderDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<AssetFolderDomainObject.State>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/ReadAsync"))
{
var existing =
await Collection.Find(x => x.DocumentId == key)
.FirstOrDefaultAsync();
.FirstOrDefaultAsync(ct);
if (existing != null)
{
@ -39,17 +52,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteAsync(DomainId key, AssetFolderDomainObject.State value, long oldVersion, long newVersion)
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteAsync(DomainId key, AssetFolderDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/WriteAsync"))
{
var entity = Map(value);
await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity);
await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity, ct);
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, AssetFolderDomainObject.State Value, long Version)> snapshots)
async Task ISnapshotStore<AssetFolderDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, AssetFolderDomainObject.State Value, long Version)> snapshots,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/WriteManyAsync"))
{
@ -66,24 +81,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return;
}
await Collection.BulkWriteAsync(updates, BulkUnordered);
await Collection.BulkWriteAsync(updates, BulkUnordered, ct);
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.ReadAllAsync(Func<AssetFolderDomainObject.State, long, Task> callback,
async Task ISnapshotStore<AssetFolderDomainObject.State>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/ReadAllAsync"))
{
await Collection.Find(new BsonDocument(), Batching.Options).ForEachAsync(x => callback(Map(x), x.Version), ct);
}
}
async Task ISnapshotStore<AssetFolderDomainObject.State>.RemoveAsync(DomainId key)
{
using (Telemetry.Activities.StartActivity("MongoAssetFolderRepository/RemoveAsync"))
{
await Collection.DeleteOneAsync(x => x.DocumentId == key);
await Collection.DeleteOneAsync(x => x.DocumentId == key, ct);
}
}

5
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection,
CancellationToken ct = default)
CancellationToken ct)
{
return collection.Indexes.CreateManyAsync(new[]
{
@ -142,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return ResultList.Create<IAssetEntity>(assetTotal, assetEntities);
}
}
catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal))
{
throw new DomainException(T.Get("common.resultTooLarge"));
}

43
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
@ -20,15 +20,28 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetDomainObject.State>
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetDomainObject.State>, IDeleter
{
async Task<(AssetDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<AssetDomainObject.State>.ReadAsync(DomainId key)
Task IDeleter.DeleteAppAsync(IAppEntity app,
CancellationToken ct)
{
return Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, app.Id), ct);
}
IAsyncEnumerable<(AssetDomainObject.State State, long Version)> ISnapshotStore<AssetDomainObject.State>.ReadAllAsync(
CancellationToken ct)
{
return Collection.Find(new BsonDocument(), Batching.Options).ToAsyncEnumerable(ct).Select(x => (Map(x), x.Version));
}
async Task<(AssetDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<AssetDomainObject.State>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/ReadAsync"))
{
var existing =
await Collection.Find(x => x.DocumentId == key)
.FirstOrDefaultAsync();
.FirstOrDefaultAsync(ct);
if (existing != null)
{
@ -39,17 +52,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
}
async Task ISnapshotStore<AssetDomainObject.State>.WriteAsync(DomainId key, AssetDomainObject.State value, long oldVersion, long newVersion)
async Task ISnapshotStore<AssetDomainObject.State>.WriteAsync(DomainId key, AssetDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/WriteAsync"))
{
var entity = Map(value);
await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity);
await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity, ct);
}
}
async Task ISnapshotStore<AssetDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, AssetDomainObject.State Value, long Version)> snapshots)
async Task ISnapshotStore<AssetDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, AssetDomainObject.State Value, long Version)> snapshots,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/WriteManyAsync"))
{
@ -66,24 +81,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
return;
}
await Collection.BulkWriteAsync(updates, BulkUnordered);
await Collection.BulkWriteAsync(updates, BulkUnordered, ct);
}
}
async Task ISnapshotStore<AssetDomainObject.State>.ReadAllAsync(Func<AssetDomainObject.State, long, Task> callback,
async Task ISnapshotStore<AssetDomainObject.State>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/ReadAllAsync"))
{
await Collection.Find(new BsonDocument(), Batching.Options).ForEachAsync(x => callback(Map(x), x.Version), ct);
}
}
async Task ISnapshotStore<AssetDomainObject.State>.RemoveAsync(DomainId key)
{
using (Telemetry.Activities.StartActivity("MongoAssetRepository/RemoveAsync"))
{
await Collection.DeleteOneAsync(x => x.DocumentId == key);
await Collection.DeleteOneAsync(x => x.DocumentId == key, ct);
}
}

44
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -103,6 +103,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return queryAsStream.StreamAll(appId, schemaIds, ct);
}
public IAsyncEnumerable<IContentEntity> QueryScheduledWithoutDataAsync(Instant now,
CancellationToken ct)
{
return queryScheduled.QueryAsync(now, ct);
}
public async Task DeleteAppAsync(DomainId appId,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/DeleteAppAsync"))
{
await Collection.DeleteManyAsync(Filter.Eq(x => x.IndexedAppId, appId), ct);
}
}
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, List<ISchemaEntity> schemas, Q q,
CancellationToken ct)
{
@ -165,15 +180,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public async Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentCollection/QueryScheduledWithoutDataAsync"))
{
await queryScheduled.QueryAsync(now, callback, ct);
}
}
public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id, Status Status)>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids,
CancellationToken ct)
{
@ -201,24 +207,28 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public async Task<long> FindVersionAsync(DomainId documentId)
public async Task<long> FindVersionAsync(DomainId documentId,
CancellationToken ct = default)
{
var result = await Collection.Find(x => x.DocumentId == documentId).Only(x => x.Version).FirstOrDefaultAsync();
var result = await Collection.Find(x => x.DocumentId == documentId).Only(x => x.Version).FirstOrDefaultAsync(ct);
return result?["vs"].AsInt64 ?? EtagVersion.Empty;
}
public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity)
public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity,
CancellationToken ct = default)
{
return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity);
return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity, ct);
}
public Task RemoveAsync(DomainId documentId)
public Task RemoveAsync(DomainId documentId,
CancellationToken ct = default)
{
return Collection.DeleteOneAsync(x => x.DocumentId == documentId);
return Collection.DeleteOneAsync(x => x.DocumentId == documentId, ct);
}
public Task InsertManyAsync(IReadOnlyList<MongoContentEntity> entities)
public Task InsertManyAsync(IReadOnlyList<MongoContentEntity> entities,
CancellationToken ct = default)
{
if (entities.Count == 0)
{
@ -232,7 +242,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
IsUpsert = true
}).ToList();
return Collection.BulkWriteAsync(writes, BulkUnordered);
return Collection.BulkWriteAsync(writes, BulkUnordered, ct);
}
}
}

15
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -47,7 +47,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
this.appProvider = appProvider;
}
public async Task InitializeAsync(CancellationToken ct = default)
public async Task InitializeAsync(
CancellationToken ct)
{
await collectionAll.InitializeAsync(ct);
await collectionPublished.InitializeAsync(ct);
@ -59,6 +60,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return collectionAll.StreamAll(appId, schemaIds, ct);
}
public IAsyncEnumerable<IContentEntity> QueryScheduledWithoutDataAsync(Instant now,
CancellationToken ct = default)
{
return collectionAll.QueryScheduledWithoutDataAsync(now, ct);
}
public Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, List<ISchemaEntity> schemas, Q q, SearchScope scope,
CancellationToken ct = default)
{
@ -130,12 +137,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return collectionAll.ResetScheduledAsync(documentId, ct);
}
public Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback,
CancellationToken ct = default)
{
return collectionAll.QueryScheduledWithoutDataAsync(now, callback, ct);
}
public Task<IReadOnlyList<(DomainId SchemaId, DomainId Id, Status Status)>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode,
CancellationToken ct = default)
{

74
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -5,12 +5,13 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents.DomainObject;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
@ -18,43 +19,57 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject.State>
public partial class MongoContentRepository : ISnapshotStore<ContentDomainObject.State>, IDeleter
{
Task ISnapshotStore<ContentDomainObject.State>.ReadAllAsync(Func<ContentDomainObject.State, long, Task> callback,
IAsyncEnumerable<(ContentDomainObject.State State, long Version)> ISnapshotStore<ContentDomainObject.State>.ReadAllAsync(
CancellationToken ct)
{
return Task.CompletedTask;
return AsyncEnumerable.Empty<(ContentDomainObject.State State, long Version)>();
}
async Task<(ContentDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<ContentDomainObject.State>.ReadAsync(DomainId key)
async Task<(ContentDomainObject.State Value, bool Valid, long Version)> ISnapshotStore<ContentDomainObject.State>.ReadAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/ReadAsync"))
{
var version = await collectionAll.FindVersionAsync(key);
var version = await collectionAll.FindVersionAsync(key, ct);
return (null!, false, version);
}
}
async Task ISnapshotStore<ContentDomainObject.State>.ClearAsync()
async Task IDeleter.DeleteAppAsync(IAppEntity app,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/DeleteAppAsync"))
{
await collectionAll.DeleteAppAsync(app.Id, ct);
await collectionPublished.DeleteAppAsync(app.Id, ct);
}
}
async Task ISnapshotStore<ContentDomainObject.State>.ClearAsync(
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/ClearAsync"))
{
await collectionAll.ClearAsync();
await collectionPublished.ClearAsync();
await collectionAll.ClearAsync(ct);
await collectionPublished.ClearAsync(ct);
}
}
async Task ISnapshotStore<ContentDomainObject.State>.RemoveAsync(DomainId key)
async Task ISnapshotStore<ContentDomainObject.State>.RemoveAsync(DomainId key,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/RemoveAsync"))
{
await collectionAll.RemoveAsync(key);
await collectionPublished.RemoveAsync(key);
await collectionAll.RemoveAsync(key, ct);
await collectionPublished.RemoveAsync(key, ct);
}
}
async Task ISnapshotStore<ContentDomainObject.State>.WriteAsync(DomainId key, ContentDomainObject.State value, long oldVersion, long newVersion)
async Task ISnapshotStore<ContentDomainObject.State>.WriteAsync(DomainId key, ContentDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteAsync"))
{
@ -64,12 +79,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
await Task.WhenAll(
UpsertDraftContentAsync(value, oldVersion, newVersion),
UpsertOrDeletePublishedAsync(value, oldVersion, newVersion));
UpsertDraftContentAsync(value, oldVersion, newVersion, ct),
UpsertOrDeletePublishedAsync(value, oldVersion, newVersion, ct));
}
}
async Task ISnapshotStore<ContentDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, ContentDomainObject.State Value, long Version)> snapshots)
async Task ISnapshotStore<ContentDomainObject.State>.WriteManyAsync(IEnumerable<(DomainId Key, ContentDomainObject.State Value, long Version)> snapshots,
CancellationToken ct)
{
using (Telemetry.Activities.StartActivity("MongoContentRepository/WriteManyAsync"))
{
@ -87,42 +103,46 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
await Task.WhenAll(
collectionPublished.InsertManyAsync(entitiesPublished),
collectionAll.InsertManyAsync(entitiesAll));
collectionPublished.InsertManyAsync(entitiesPublished, ct),
collectionAll.InsertManyAsync(entitiesAll, ct));
}
}
private async Task UpsertOrDeletePublishedAsync(ContentDomainObject.State value, long oldVersion, long newVersion)
private async Task UpsertOrDeletePublishedAsync(ContentDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct = default)
{
if (ShouldWritePublished(value))
{
await UpsertPublishedContentAsync(value, oldVersion, newVersion);
await UpsertPublishedContentAsync(value, oldVersion, newVersion, ct);
}
else
{
await DeletePublishedContentAsync(value.AppId.Id, value.Id);
await DeletePublishedContentAsync(value.AppId.Id, value.Id, ct);
}
}
private Task DeletePublishedContentAsync(DomainId appId, DomainId id)
private Task DeletePublishedContentAsync(DomainId appId, DomainId id,
CancellationToken ct = default)
{
var documentId = DomainId.Combine(appId, id);
return collectionPublished.RemoveAsync(documentId);
return collectionPublished.RemoveAsync(documentId, ct);
}
private async Task UpsertDraftContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion)
private async Task UpsertDraftContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct = default)
{
var entity = await CreateDraftContentAsync(value, newVersion);
await collectionAll.UpsertVersionedAsync(entity.DocumentId, oldVersion, entity);
await collectionAll.UpsertVersionedAsync(entity.DocumentId, oldVersion, entity, ct);
}
private async Task UpsertPublishedContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion)
private async Task UpsertPublishedContentAsync(ContentDomainObject.State value, long oldVersion, long newVersion,
CancellationToken ct = default)
{
var entity = await CreatePublishedContentAsync(value, newVersion);
await collectionPublished.UpsertVersionedAsync(entity.DocumentId, oldVersion, entity);
await collectionPublished.UpsertVersionedAsync(entity.DocumentId, oldVersion, entity, ct);
}
private async Task<MongoContentEntity> CreatePublishedContentAsync(ContentDomainObject.State value, long newVersion)

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs

@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
}
else
{
var first = documentIds.First();
var first = documentIds[0];
filters.Add(
Filter.Or(

9
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -64,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
try
{
var schema = await appProvider.GetSchemaAsync(appId, schemaId);
var schema = await appProvider.GetSchemaAsync(appId, schemaId, ct: ct);
if (schema == null)
{
@ -81,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
throw new DomainException(T.Get("common.resultTooLarge"));
}
catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal))
{
throw new DomainException(T.Get("common.resultTooLarge"));
}
@ -117,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
throw new DomainException(T.Get("common.resultTooLarge"));
}
catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal))
{
throw new DomainException(T.Get("common.resultTooLarge"));
}
@ -154,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
throw new DomainException(T.Get("common.resultTooLarge"));
}
catch (MongoQueryException ex) when (ex.Message.Contains("17406"))
catch (MongoQueryException ex) when (ex.Message.Contains("17406", StringComparison.Ordinal))
{
throw new DomainException(T.Get("common.resultTooLarge"));
}

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs

@ -16,7 +16,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal class QueryReferences : OperationBase
internal sealed class QueryReferences : OperationBase
{
private static readonly IResultList<IContentEntity> EmptyIds = ResultList.CreateFrom<IContentEntity>(0);
private readonly QueryByIds queryByIds;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save