Browse Source

WIP: Feature/string-identifiers (#524)

* Global ids for all elements
pull/568/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
1506f2ba98
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs
  2. 4
      backend/extensions/Squidex.Extensions/Actions/Email/EmailActionHandler.cs
  3. 4
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  4. 2
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  5. 6
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs
  6. 25
      backend/src/Migrations/MigrationPath.cs
  7. 60
      backend/src/Migrations/Migrations/AddPatterns.cs
  8. 30
      backend/src/Migrations/Migrations/ClearRules.cs
  9. 3
      backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs
  10. 10
      backend/src/Migrations/Migrations/CreateAssetSlugs.cs
  11. 118
      backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs
  12. 19
      backend/src/Migrations/Migrations/PopulateGrainIndexes.cs
  13. 28
      backend/src/Migrations/Migrations/RebuildAssetFolders.cs
  14. 11
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  15. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs
  16. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs
  17. 5
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs
  18. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
  19. 9
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusTypeConverter.cs
  20. 7
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs
  21. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs
  22. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs
  23. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs
  24. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs
  25. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs
  26. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs
  27. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs
  28. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs
  29. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchemaV2.cs
  30. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  31. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  32. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  33. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs
  34. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  35. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  36. 15
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  37. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  38. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  39. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  40. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
  41. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs
  42. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs
  43. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/SchemaBuilder.cs
  44. 13
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs
  45. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs
  46. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleTriggerHandler.cs
  47. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs
  48. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  49. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  50. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs
  51. 43
      backend/src/Squidex.Domain.Apps.Core.Operations/IUrlGenerator.cs
  52. 7
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs
  53. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  54. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs
  55. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs
  56. 16
      backend/src/Squidex.Domain.Apps.Core.Operations/Tags/ITagService.cs
  57. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagGroups.cs
  58. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs
  59. 1
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs
  60. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs
  61. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs
  62. 28
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  63. 19
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  64. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  65. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  66. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  67. 31
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  68. 30
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs
  69. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  70. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs
  71. 43
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  72. 17
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs
  73. 8
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  74. 22
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionAll.cs
  75. 16
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs
  76. 44
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  77. 10
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  78. 31
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  79. 7
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContent.cs
  80. 18
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs
  81. 20
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs
  82. 24
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs
  83. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs
  84. 3
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoIndexStorage.cs
  85. 61
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndexState.cs
  86. 43
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndexerState.cs
  87. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  88. 18
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs
  89. 32
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  90. 15
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleStatisticsCollection.cs
  91. 15
      backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs
  92. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  93. 35
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
  94. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs
  95. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  96. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs
  97. 13
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs
  98. 9
      backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  99. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs
  100. 8
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs

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

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
@ -74,7 +73,7 @@ namespace Squidex.Extensions.Actions.Comment
public sealed class CommentJob public sealed class CommentJob
{ {
public NamedId<Guid> AppId { get; set; } public NamedId<DomainId> AppId { get; set; }
public RefToken Actor { get; set; } public RefToken Actor { get; set; }

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

@ -69,13 +69,13 @@ namespace Squidex.Extensions.Actions.Email
return Result.Complete(); return Result.Complete();
} }
private async Task CheckConnectionAsync(EmailJob job, CancellationToken ct) private static async Task CheckConnectionAsync(EmailJob job, CancellationToken ct)
{ {
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{ {
var tcs = new TaskCompletionSource<IAsyncResult>(); var tcs = new TaskCompletionSource<IAsyncResult>();
var state = socket.BeginConnect(job.ServerHost, job.ServerPort, tcs.SetResult, null); socket.BeginConnect(job.ServerHost, job.ServerPort, tcs.SetResult, null);
using (ct.Register(() => using (ct.Register(() =>
{ {

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

@ -131,7 +131,7 @@ namespace Squidex.Extensions.Actions.Kafka
} }
} }
private 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; message.Key = job.MessageKey;
@ -181,7 +181,7 @@ namespace Squidex.Extensions.Actions.Kafka
avroProducer?.Dispose(); avroProducer?.Dispose();
} }
private object GetValue(IJsonValue value, Schema schema) private static object GetValue(IJsonValue value, Schema schema)
{ {
switch (value) switch (value)
{ {

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

@ -21,7 +21,7 @@ namespace Squidex.Extensions.Actions.Notification
public sealed class NotificationActionHandler : RuleActionHandler<NotificationAction, NotificationJob> public sealed class NotificationActionHandler : RuleActionHandler<NotificationAction, NotificationJob>
{ {
private const string Description = "Send a Notification"; private const string Description = "Send a Notification";
private static readonly NamedId<Guid> NoApp = NamedId.Of(Guid.Empty, "none"); private static readonly NamedId<DomainId> NoApp = NamedId.Of(DomainId.Empty, "none");
private readonly ICommandBus commandBus; private readonly ICommandBus commandBus;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;

6
backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs

@ -78,15 +78,15 @@ namespace Squidex.Extensions.Validation
{ {
case BooleanFieldProperties _ when value is JsonBoolean boolean: case BooleanFieldProperties _ when value is JsonBoolean boolean:
return boolean.Value; return boolean.Value;
case BooleanFieldProperties _ when value is JsonNull _: case BooleanFieldProperties _ when value is JsonNull:
return ClrValue.Null; return ClrValue.Null;
case NumberFieldProperties _ when value is JsonNumber number: case NumberFieldProperties _ when value is JsonNumber number:
return number.Value; return number.Value;
case NumberFieldProperties _ when value is JsonNull _: case NumberFieldProperties _ when value is JsonNull:
return ClrValue.Null; return ClrValue.Null;
case StringFieldProperties _ when value is JsonString @string: case StringFieldProperties _ when value is JsonString @string:
return @string.Value; return @string.Value;
case StringFieldProperties _ when value is JsonNull _: case StringFieldProperties _ when value is JsonNull:
return ClrValue.Null; return ClrValue.Null;
case ReferencesFieldProperties _ when value is JsonArray array && array.FirstOrDefault() is JsonString @string: case ReferencesFieldProperties _ when value is JsonArray array && array.FirstOrDefault() is JsonString @string:
return @string.Value; return @string.Value;

25
backend/src/Migrations/MigrationPath.cs

@ -18,7 +18,7 @@ namespace Migrations
{ {
public sealed class MigrationPath : IMigrationPath public sealed class MigrationPath : IMigrationPath
{ {
private const int CurrentVersion = 21; private const int CurrentVersion = 22;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
public MigrationPath(IServiceProvider serviceProvider) public MigrationPath(IServiceProvider serviceProvider)
@ -48,6 +48,12 @@ namespace Migrations
yield return serviceProvider.GetRequiredService<ConvertEventStore>(); yield return serviceProvider.GetRequiredService<ConvertEventStore>();
} }
// Version 22: Also add app id to aggregate id.
if (version < 22)
{
yield return serviceProvider.GetRequiredService<AddAppIdToEventStream>();
}
// Version 07: Introduces AppId for backups. // Version 07: Introduces AppId for backups.
else if (version < 7) else if (version < 7)
{ {
@ -74,14 +80,18 @@ namespace Migrations
} }
// Version 14: Schema refactoring // Version 14: Schema refactoring
if (version < 14) // Version 22: Also add app id to aggregate id.
if (version < 22)
{ {
yield return serviceProvider.GetRequiredService<ClearSchemas>(); yield return serviceProvider.GetRequiredService<ClearSchemas>();
yield return serviceProvider.GetRequiredService<ClearRules>();
} }
// Version 18: Rebuild assets. // Version 18: Rebuild assets.
if (version < 18) // Version 22: Introduce domain id.
if (version < 22)
{ {
yield return serviceProvider.GetService<RebuildAssetFolders>();
yield return serviceProvider.GetService<RebuildAssets>(); yield return serviceProvider.GetService<RebuildAssets>();
} }
else else
@ -100,18 +110,13 @@ namespace Migrations
} }
// Version 21: Introduce content drafts V2. // Version 21: Introduce content drafts V2.
if (version < 21) // Version 22: Introduce domain id.
if (version < 22)
{ {
yield return serviceProvider.GetRequiredService<RebuildContents>(); yield return serviceProvider.GetRequiredService<RebuildContents>();
} }
} }
// Version 01: Introduce app patterns.
if (version < 1)
{
yield return serviceProvider.GetRequiredService<AddPatterns>();
}
// Version 13: Json refactoring // Version 13: Json refactoring
if (version < 13) if (version < 13)
{ {

60
backend/src/Migrations/Migrations/AddPatterns.cs

@ -1,60 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations
{
public sealed class AddPatterns : IMigration
{
private readonly InitialPatterns initialPatterns;
private readonly ICommandBus commandBus;
private readonly IAppsIndex indexForApps;
public AddPatterns(InitialPatterns initialPatterns, ICommandBus commandBus, IAppsIndex indexForApps)
{
this.indexForApps = indexForApps;
this.initialPatterns = initialPatterns;
this.commandBus = commandBus;
}
public async Task UpdateAsync()
{
var ids = await indexForApps.GetIdsAsync();
foreach (var id in ids)
{
var app = await indexForApps.GetAppAsync(id);
if (app != null && app.Patterns.Count == 0)
{
foreach (var pattern in initialPatterns.Values)
{
var command =
new AddPattern
{
Actor = app.CreatedBy,
AppId = id,
Name = pattern.Name,
PatternId = Guid.NewGuid(),
Pattern = pattern.Pattern,
Message = pattern.Message
};
await commandBus.PublishAsync(command);
}
}
}
}
}
}

30
backend/src/Migrations/Migrations/ClearRules.cs

@ -0,0 +1,30 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Rules.State;
using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States;
namespace Migrations.Migrations
{
public sealed class ClearRules : IMigration
{
private readonly IStore<Guid> store;
public ClearRules(IStore<Guid> store)
{
this.store = store;
}
public Task UpdateAsync()
{
return store.ClearSnapshotsAsync<Guid, RuleState>();
}
}
}

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

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
@ -67,7 +66,7 @@ namespace Migrations.Migrations
{ {
var appId = NamedId<Guid>.Parse(appIdValue.ToString(), Guid.TryParse).Id.ToString(); var appId = NamedId<Guid>.Parse(appIdValue.ToString(), Guid.TryParse).Id.ToString();
var eventUpdate = updater.Set($"Events.{index}.Metadata.{SquidexHeaders.AppId}", appId); var eventUpdate = updater.Set($"Events.{index}.Metadata.AppId", appId);
if (update != null) if (update != null)
{ {

10
backend/src/Migrations/Migrations/CreateAssetSlugs.cs

@ -5,10 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Migrations;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -16,9 +16,9 @@ namespace Migrations.Migrations
{ {
public sealed class CreateAssetSlugs : IMigration public sealed class CreateAssetSlugs : IMigration
{ {
private readonly ISnapshotStore<AssetState, Guid> stateForAssets; private readonly ISnapshotStore<AssetState, string> stateForAssets;
public CreateAssetSlugs(ISnapshotStore<AssetState, Guid> stateForAssets) public CreateAssetSlugs(ISnapshotStore<AssetState, string> stateForAssets)
{ {
this.stateForAssets = stateForAssets; this.stateForAssets = stateForAssets;
} }
@ -29,7 +29,9 @@ namespace Migrations.Migrations
{ {
state.Slug = state.FileName.ToAssetSlug(); state.Slug = state.FileName.ToAssetSlug();
await stateForAssets.WriteAsync(state.Id, state, version, version); var key = DomainId.Combine(state.AppId.Id, state.Id).ToString();
await stateForAssets.WriteAsync(key, state, version, version);
}); });
} }
} }

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

@ -0,0 +1,118 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations.MongoDb
{
public sealed class AddAppIdToEventStream : IMigration
{
private readonly IMongoDatabase database;
public AddAppIdToEventStream(IMongoDatabase database)
{
this.database = database;
}
public async Task UpdateAsync()
{
var collection = database.GetCollection<BsonDocument>("Events");
const int SizeOfBatch = 200;
const int SizeOfQueue = 20;
var batchBlock = new BatchBlock<BsonDocument>(SizeOfBatch, new GroupingDataflowBlockOptions
{
BoundedCapacity = SizeOfQueue * SizeOfBatch
});
var actionBlock = new ActionBlock<BsonDocument[]>(async batch =>
{
var updates = new List<WriteModel<BsonDocument>>();
foreach (var commit in batch)
{
var eventStream = commit["EventStream"].AsString;
string? appId = null;
foreach (var @event in commit["Events"].AsBsonArray)
{
var metadata = @event["Metadata"].AsBsonDocument;
if (metadata.TryGetValue("AppId", out var value))
{
appId = value.AsString;
}
}
if (appId != null)
{
var parts = eventStream.Split("-");
var domainType = parts[0];
var domainId = string.Join("-", parts.Skip(1));
var newStreamName = $"{domainType}-{appId}--{domainId}";
var update = Builders<BsonDocument>.Update.Set("EventStream", newStreamName);
var i = 0;
foreach (var @event in commit["Events"].AsBsonArray)
{
update = update.Set($"Events.{i}.Metadata.AggregateId", $"{appId}--{domainId}");
update = update.Unset($"Events.{i}.Metadata.AppId");
i++;
}
var filter = Builders<BsonDocument>.Filter.Eq("_id", commit["_id"].AsString);
updates.Add(new UpdateOneModel<BsonDocument>(filter, update));
}
}
if (updates.Count > 0)
{
await collection.BulkWriteAsync(updates);
}
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 4,
MaxMessagesPerTask = 1,
BoundedCapacity = SizeOfQueue
});
batchBlock.LinkTo(actionBlock, new DataflowLinkOptions
{
PropagateCompletion = true
});
await collection.Find(new BsonDocument()).ForEachAsync(async commit =>
{
var eventStream = commit["EventStream"].AsString;
if (!eventStream.Contains("--") && !eventStream.StartsWith("app-", StringComparison.OrdinalIgnoreCase))
{
await batchBlock.SendAsync(commit);
}
});
batchBlock.Complete();
await actionBlock.Completion;
}
}
}

19
backend/src/Migrations/Migrations/PopulateGrainIndexes.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
@ -50,20 +49,20 @@ namespace Migrations.Migrations
private async Task RebuildAppIndexes() private async Task RebuildAppIndexes()
{ {
var appsByName = new Dictionary<string, Guid>(); var appsByName = new Dictionary<string, DomainId>();
var appsByUser = new Dictionary<string, HashSet<Guid>>(); var appsByUser = new Dictionary<string, HashSet<DomainId>>();
bool HasApp(NamedId<Guid> appId, bool consistent, out Guid id) bool HasApp(NamedId<DomainId> appId, bool consistent, out DomainId id)
{ {
return appsByName!.TryGetValue(appId.Name, out id) && (!consistent || id == appId.Id); return appsByName!.TryGetValue(appId.Name, out id) && (!consistent || id == appId.Id);
} }
HashSet<Guid> Index(string contributorId) HashSet<DomainId> Index(string contributorId)
{ {
return appsByUser!.GetOrAddNew(contributorId); return appsByUser!.GetOrAddNew(contributorId);
} }
void RemoveApp(NamedId<Guid> appId, bool consistent) void RemoveApp(NamedId<DomainId> appId, bool consistent)
{ {
if (HasApp(appId, consistent, out var id)) if (HasApp(appId, consistent, out var id))
{ {
@ -121,9 +120,9 @@ namespace Migrations.Migrations
private async Task RebuildRuleIndexes() private async Task RebuildRuleIndexes()
{ {
var rulesByApp = new Dictionary<Guid, HashSet<Guid>>(); var rulesByApp = new Dictionary<DomainId, HashSet<DomainId>>();
HashSet<Guid> Index(RuleEvent @event) HashSet<DomainId> Index(RuleEvent @event)
{ {
return rulesByApp!.GetOrAddNew(@event.AppId.Id); return rulesByApp!.GetOrAddNew(@event.AppId.Id);
} }
@ -153,9 +152,9 @@ namespace Migrations.Migrations
private async Task RebuildSchemaIndexes() private async Task RebuildSchemaIndexes()
{ {
var schemasByApp = new Dictionary<Guid, Dictionary<string, Guid>>(); var schemasByApp = new Dictionary<DomainId, Dictionary<string, DomainId>>();
Dictionary<string, Guid> Index(SchemaEvent @event) Dictionary<string, DomainId> Index(SchemaEvent @event)
{ {
return schemasByApp!.GetOrAddNew(@event.AppId.Id); return schemasByApp!.GetOrAddNew(@event.AppId.Id);
} }

28
backend/src/Migrations/Migrations/RebuildAssetFolders.cs

@ -0,0 +1,28 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Migrations;
namespace Migrations.Migrations
{
public sealed class RebuildAssetFolders : IMigration
{
private readonly Rebuilder rebuilder;
public RebuildAssetFolders(Rebuilder rebuilder)
{
this.rebuilder = rebuilder;
}
public Task UpdateAsync()
{
return rebuilder.RebuildAssetFoldersAsync();
}
}
}

11
backend/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -13,7 +12,7 @@ using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppPatterns : ImmutableDictionary<Guid, AppPattern> public sealed class AppPatterns : ImmutableDictionary<DomainId, AppPattern>
{ {
public static readonly AppPatterns Empty = new AppPatterns(); public static readonly AppPatterns Empty = new AppPatterns();
@ -21,19 +20,19 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
} }
public AppPatterns(Dictionary<Guid, AppPattern> inner) public AppPatterns(Dictionary<DomainId, AppPattern> inner)
: base(inner) : base(inner)
{ {
} }
[Pure] [Pure]
public AppPatterns Remove(Guid id) public AppPatterns Remove(DomainId id)
{ {
return Without<AppPatterns>(id); return Without<AppPatterns>(id);
} }
[Pure] [Pure]
public AppPatterns Add(Guid id, string name, string pattern, string? message = null) public AppPatterns Add(DomainId id, string name, string pattern, string? message = null)
{ {
var newPattern = new AppPattern(name, pattern, message); var newPattern = new AppPattern(name, pattern, message);
@ -41,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Apps
} }
[Pure] [Pure]
public AppPatterns Update(Guid id, string name, string pattern, string? message = null) public AppPatterns Update(DomainId id, string name, string pattern, string? message = null)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNullOrEmpty(pattern, nameof(pattern)); Guard.NotNullOrEmpty(pattern, nameof(pattern));

5
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
protected override void WriteValue(JsonWriter writer, AppPatterns value, JsonSerializer serializer) protected override void WriteValue(JsonWriter writer, AppPatterns value, JsonSerializer serializer)
{ {
var json = new Dictionary<Guid, JsonAppPattern>(value.Count); var json = new Dictionary<DomainId, JsonAppPattern>(value.Count);
foreach (var (key, appPattern) in value) foreach (var (key, appPattern) in value)
{ {
@ -29,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) protected override AppPatterns ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<Dictionary<Guid, JsonAppPattern>>(reader)!; var json = serializer.Deserialize<Dictionary<DomainId, JsonAppPattern>>(reader)!;
return new AppPatterns(json.ToDictionary(x => x.Key, x => x.Value.ToPattern())); return new AppPatterns(json.ToDictionary(x => x.Key, x => x.Value.ToPattern()));
} }

4
backend/src/Squidex.Domain.Apps.Core.Model/Comments/Comment.cs

@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Core.Comments
{ {
public sealed class Comment public sealed class Comment
{ {
public Guid Id { get; } public DomainId Id { get; }
public Instant Time { get; } public Instant Time { get; }
@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.Comments
public Uri? Url { get; } public Uri? Url { get; }
public Comment(Guid id, Instant time, RefToken user, string text, Uri? url = null) public Comment(DomainId id, Instant time, RefToken user, string text, Uri? url = null)
{ {
Guard.NotEmpty(id, nameof(id)); Guard.NotEmpty(id, nameof(id));
Guard.NotNull(user, nameof(user)); Guard.NotNull(user, nameof(user));

5
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/WorkflowConverter.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Contents.Json namespace Squidex.Domain.Apps.Core.Contents.Json
@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
{ {
protected override void WriteValue(JsonWriter writer, Workflows value, JsonSerializer serializer) protected override void WriteValue(JsonWriter writer, Workflows value, JsonSerializer serializer)
{ {
var json = new Dictionary<Guid, Workflow>(value.Count); var json = new Dictionary<DomainId, Workflow>(value.Count);
foreach (var (key, workflow) in value) foreach (var (key, workflow) in value)
{ {
@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
protected override Workflows ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) protected override Workflows ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<Dictionary<Guid, Workflow>>(reader); var json = serializer.Deserialize<Dictionary<DomainId, Workflow>>(reader);
return new Workflows(json!); return new Workflows(json!);
} }

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

@ -11,8 +11,8 @@ using System.Diagnostics.CodeAnalysis;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
[TypeConverter(typeof(StatusConverter))] [TypeConverter(typeof(StatusTypeConverter))]
public struct Status : IEquatable<Status>, IComparable<Status> public readonly struct Status : IEquatable<Status>, IComparable<Status>
{ {
public static readonly Status Archived = new Status("Archived"); public static readonly Status Archived = new Status("Archived");
public static readonly Status Draft = new Status("Draft"); public static readonly Status Draft = new Status("Draft");
@ -37,12 +37,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public bool Equals(Status other) public bool Equals(Status other)
{ {
return string.Equals(name, other.name); return string.Equals(Name, other.Name);
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return name?.GetHashCode() ?? 0; return Name.GetHashCode();
} }
public override string ToString() public override string ToString()

9
backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusConverter.cs → backend/src/Squidex.Domain.Apps.Core.Model/Contents/StatusTypeConverter.cs

@ -11,7 +11,7 @@ using System.Globalization;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class StatusConverter : TypeConverter public sealed class StatusTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{ {
@ -25,7 +25,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
return new Status(value?.ToString()); if (value is string s)
{
return new Status(s);
}
return default(Status);
} }
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)

7
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflow.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -20,21 +19,21 @@ namespace Squidex.Domain.Apps.Core.Contents
private const string DefaultName = "Unnamed"; private const string DefaultName = "Unnamed";
public static readonly IReadOnlyDictionary<Status, WorkflowStep> EmptySteps = new Dictionary<Status, WorkflowStep>(); public static readonly IReadOnlyDictionary<Status, WorkflowStep> EmptySteps = new Dictionary<Status, WorkflowStep>();
public static readonly IReadOnlyList<Guid> EmptySchemaIds = new List<Guid>(); public static readonly IReadOnlyList<DomainId> EmptySchemaIds = new List<DomainId>();
public static readonly Workflow Default = CreateDefault(); public static readonly Workflow Default = CreateDefault();
public static readonly Workflow Empty = new Workflow(default, EmptySteps); public static readonly Workflow Empty = new Workflow(default, EmptySteps);
[IgnoreDuringEquals] [IgnoreDuringEquals]
public IReadOnlyDictionary<Status, WorkflowStep> Steps { get; } = EmptySteps; public IReadOnlyDictionary<Status, WorkflowStep> Steps { get; } = EmptySteps;
public IReadOnlyList<Guid> SchemaIds { get; } = EmptySchemaIds; public IReadOnlyList<DomainId> SchemaIds { get; } = EmptySchemaIds;
public Status Initial { get; } public Status Initial { get; }
public Workflow( public Workflow(
Status initial, Status initial,
IReadOnlyDictionary<Status, WorkflowStep>? steps, IReadOnlyDictionary<Status, WorkflowStep>? steps,
IReadOnlyList<Guid>? schemaIds = null, IReadOnlyList<DomainId>? schemaIds = null,
string? name = null) string? name = null)
: base(name ?? DefaultName) : base(name ?? DefaultName)
{ {

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
@ -14,7 +13,7 @@ using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class Workflows : ImmutableDictionary<Guid, Workflow> public sealed class Workflows : ImmutableDictionary<DomainId, Workflow>
{ {
public static readonly Workflows Empty = new Workflows(); public static readonly Workflows Empty = new Workflows();
@ -22,19 +21,19 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
} }
public Workflows(Dictionary<Guid, Workflow> inner) public Workflows(Dictionary<DomainId, Workflow> inner)
: base(inner) : base(inner)
{ {
} }
[Pure] [Pure]
public Workflows Remove(Guid id) public Workflows Remove(DomainId id)
{ {
return Without<Workflows>(id); return Without<Workflows>(id);
} }
[Pure] [Pure]
public Workflows Add(Guid workflowId, string name) public Workflows Add(DomainId workflowId, string name)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
@ -46,11 +45,11 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
Guard.NotNull(workflow, nameof(workflow)); Guard.NotNull(workflow, nameof(workflow));
return With<Workflows>(Guid.Empty, workflow); return With<Workflows>(default, workflow);
} }
[Pure] [Pure]
public Workflows Set(Guid id, Workflow workflow) public Workflows Set(DomainId id, Workflow workflow)
{ {
Guard.NotNull(workflow, nameof(workflow)); Guard.NotNull(workflow, nameof(workflow));
@ -58,11 +57,11 @@ namespace Squidex.Domain.Apps.Core.Contents
} }
[Pure] [Pure]
public Workflows Update(Guid id, Workflow workflow) public Workflows Update(DomainId id, Workflow workflow)
{ {
Guard.NotNull(workflow, nameof(workflow)); Guard.NotNull(workflow, nameof(workflow));
if (id == Guid.Empty) if (id == DomainId.Empty)
{ {
return Set(workflow); return Set(workflow);
} }

3
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -16,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public EnrichedAssetEventType Type { get; set; } public EnrichedAssetEventType Type { get; set; }
public Guid Id { get; set; } public DomainId Id { get; set; }
public Instant Created { get; set; } public Instant Created { get; set; }

3
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedContentEvent.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -16,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public EnrichedContentEventType Type { get; set; } public EnrichedContentEventType Type { get; set; }
public Guid Id { get; set; } public DomainId Id { get; set; }
public Instant Created { get; set; } public Instant Created { get; set; }

3
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedEvent.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -13,7 +12,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedEvent public abstract class EnrichedEvent
{ {
public NamedId<Guid> AppId { get; set; } public NamedId<DomainId> AppId { get; set; }
public Instant Timestamp { get; set; } public Instant Timestamp { get; set; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEvent.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public EnrichedSchemaEventType Type { get; set; } public EnrichedSchemaEventType Type { get; set; }
public Guid Id public DomainId Id
{ {
get { return SchemaId.Id; } get { return SchemaId.Id; }
} }

3
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedSchemaEventBase.cs

@ -5,13 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase public abstract class EnrichedSchemaEventBase : EnrichedUserEventBase
{ {
public NamedId<Guid> SchemaId { get; set; } public NamedId<DomainId> SchemaId { get; set; }
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/IEnrichedEntityEvent.cs

@ -5,12 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
{ {
public interface IEnrichedEntityEvent public interface IEnrichedEntityEvent
{ {
Guid Id { get; } DomainId Id { get; }
} }
} }

8
backend/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs

@ -5,18 +5,18 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using NodaTime; using NodaTime;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public sealed class RuleJob public sealed class RuleJob
{ {
public Guid Id { get; set; } public DomainId Id { get; set; }
public Guid AppId { get; set; } public DomainId AppId { get; set; }
public Guid RuleId { get; set; } public DomainId RuleId { get; set; }
public string EventName { get; set; } public string EventName { get; set; }

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTriggerSchemaV2.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Triggers namespace Squidex.Domain.Apps.Core.Rules.Triggers
{ {
public sealed class ContentChangedTriggerSchemaV2 : Freezable public sealed class ContentChangedTriggerSchemaV2 : Freezable
{ {
public Guid SchemaId { get; set; } public DomainId SchemaId { get; set; }
public string? Condition { get; set; } public string? Condition { get; set; }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
properties.Freeze(); properties.Freeze();
} }
private T ValidateProperties(FieldProperties newProperties) private static T ValidateProperties(FieldProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));

10
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -5,10 +5,10 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
@ -25,17 +25,17 @@ namespace Squidex.Domain.Apps.Core.Schemas
public ReferencesFieldEditor Editor { get; set; } public ReferencesFieldEditor Editor { get; set; }
public Guid SchemaId public DomainId SchemaId
{ {
get get
{ {
return SchemaIds?.FirstOrDefault() ?? Guid.Empty; return SchemaIds?.FirstOrDefault() ?? default;
} }
set set
{ {
if (value != default) if (value != default)
{ {
SchemaIds = new ReadOnlyCollection<Guid>(new List<Guid> { value }); SchemaIds = new ReadOnlyCollection<DomainId>(new List<DomainId> { value });
} }
else else
{ {
@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
} }
} }
public ReadOnlyCollection<Guid>? SchemaIds { get; set; } public ReadOnlyCollection<DomainId>? SchemaIds { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs

@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
properties.Freeze(); properties.Freeze();
} }
private T ValidateProperties(FieldProperties newProperties) private static T ValidateProperties(FieldProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));

4
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaExtensions.cs

@ -59,9 +59,9 @@ namespace Squidex.Domain.Apps.Core.Schemas
return schema.Properties.Label.WithFallback(schema.Name); return schema.Properties.Label.WithFallback(schema.Name);
} }
public static Guid SingleId(this ReferencesFieldProperties properties) public static DomainId SingleId(this ReferencesFieldProperties properties)
{ {
return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : Guid.Empty; return properties.SchemaIds?.Count == 1 ? properties.SchemaIds[0] : default;
} }
public static IEnumerable<RootField> ReferenceFields(this Schema schema) public static IEnumerable<RootField> ReferenceFields(this Schema schema)

2
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -131,7 +131,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
if (field is IArrayField arrayField) if (field is IArrayField arrayField)
{ {
foreach (var (key, value) in data) foreach (var (_, value) in data)
{ {
if (value is JsonArray array) if (value is JsonArray array)
{ {

8
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Text; using System.Text;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -81,7 +82,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
}; };
} }
public static ValueConverter ResolveAssetUrls(IReadOnlyCollection<string>? fields, IUrlGenerator urlGenerator) public static ValueConverter ResolveAssetUrls(NamedId<DomainId> appId, IReadOnlyCollection<string>? fields, IUrlGenerator urlGenerator)
{ {
if (fields?.Any() != true) if (fields?.Any() != true)
{ {
@ -132,10 +133,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
var id = array[i].ToString(); var id = array[i].ToString();
if (Guid.TryParse(id, out var assetId)) array[i] = JsonValue.Create(urlGenerator.AssetContent(appId, id));
{
array[i] = JsonValue.Create(urlGenerator.AssetContent(assetId));
}
} }
} }

15
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -17,18 +16,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ContentReferencesExtensions public static class ContentReferencesExtensions
{ {
public static HashSet<Guid> GetReferencedIds(this NamedContentData source, Schema schema, int referencesPerField = int.MaxValue) public static HashSet<DomainId> GetReferencedIds(this NamedContentData source, Schema schema, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var extractor = new ReferencesExtractor(new HashSet<Guid>(), referencesPerField); var extractor = new ReferencesExtractor(new HashSet<DomainId>(), referencesPerField);
AddReferencedIds(source, schema.Fields, extractor); AddReferencedIds(source, schema.Fields, extractor);
return extractor.Result; return extractor.Result;
} }
public static void AddReferencedIds(this NamedContentData source, Schema schema, HashSet<Guid> result, int referencesPerField = int.MaxValue) public static void AddReferencedIds(this NamedContentData source, Schema schema, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -37,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
AddReferencedIds(source, schema.Fields, extractor); AddReferencedIds(source, schema.Fields, extractor);
} }
public static void AddReferencedIds(this NamedContentData source, IEnumerable<IField> fields, HashSet<Guid> result, int referencesPerField = int.MaxValue) public static void AddReferencedIds(this NamedContentData source, IEnumerable<IField> fields, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(fields, nameof(fields)); Guard.NotNull(fields, nameof(fields));
@ -46,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
AddReferencedIds(source, fields, extractor); AddReferencedIds(source, fields, extractor);
} }
public static void AddReferencedIds(this NamedContentData source, IField field, HashSet<Guid> result, int referencesPerField = int.MaxValue) public static void AddReferencedIds(this NamedContentData source, IField field, HashSet<DomainId> result, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
@ -76,9 +75,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
public static HashSet<Guid> GetReferencedIds(this IField field, IJsonValue? value, int referencesPerField = int.MaxValue) public static HashSet<DomainId> GetReferencedIds(this IField field, IJsonValue? value, int referencesPerField = int.MaxValue)
{ {
var result = new HashSet<Guid>(); var result = new HashSet<DomainId>();
if (value != null) if (value != null)
{ {

7
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -15,10 +14,10 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public sealed class ReferencesCleaner : IFieldVisitor<IJsonValue> public sealed class ReferencesCleaner : IFieldVisitor<IJsonValue>
{ {
private readonly HashSet<Guid> validIds; private readonly HashSet<DomainId> validIds;
private IJsonValue value; private IJsonValue value;
public ReferencesCleaner(HashSet<Guid> validIds) public ReferencesCleaner(HashSet<DomainId> validIds)
{ {
Guard.NotNull(validIds, nameof(validIds)); Guard.NotNull(validIds, nameof(validIds));
@ -113,7 +112,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
private bool IsValidReference(IJsonValue item) private bool IsValidReference(IJsonValue item)
{ {
return item is JsonString s && Guid.TryParse(s.Value, out var guid) && validIds.Contains(guid); return item is JsonString s && validIds.Contains(s.Value);
} }
} }
} }

8
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesExtensions public static class ReferencesExtensions
{ {
public static void AddIds(this IJsonValue? value, HashSet<Guid> result, int take) public static void AddIds(this IJsonValue? value, HashSet<DomainId> result, int take)
{ {
var added = 0; var added = 0;
@ -21,9 +21,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
foreach (var id in array) foreach (var id in array)
{ {
if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid)) if (id is JsonString s)
{ {
result.Add(guid); result.Add(s.Value);
added++; added++;

7
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -16,16 +15,16 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
internal sealed class ReferencesExtractor : IFieldVisitor<None> internal sealed class ReferencesExtractor : IFieldVisitor<None>
{ {
private readonly HashSet<Guid> result; private readonly HashSet<DomainId> result;
private readonly int take; private readonly int take;
private IJsonValue? value; private IJsonValue? value;
public HashSet<Guid> Result public HashSet<DomainId> Result
{ {
get { return result; } get { return result; }
} }
public ReferencesExtractor(HashSet<Guid> result, int take) public ReferencesExtractor(HashSet<DomainId> result, int take)
{ {
Guard.NotNull(result, nameof(result)); Guard.NotNull(result, nameof(result));

4
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs

@ -5,16 +5,16 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ValueReferencesConverter public static class ValueReferencesConverter
{ {
public static ValueConverter CleanReferences(HashSet<Guid>? validIds = null) public static ValueConverter CleanReferences(HashSet<DomainId>? validIds = null)
{ {
if (validIds == null) if (validIds == null)
{ {

2
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/ContentSchemaBuilder.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
{ {
Properties = Properties =
{ {
["id"] = SchemaBuilder.GuidProperty($"The id of the {schemaName} content.", true), ["id"] = SchemaBuilder.StringProperty($"The id of the {schemaName} content.", true),
["data"] = SchemaBuilder.ObjectProperty(dataSchema, $"The data of the {schemaName}.", true), ["data"] = SchemaBuilder.ObjectProperty(dataSchema, $"The data of the {schemaName}.", true),
["dataDraft"] = SchemaBuilder.ObjectProperty(dataSchema, $"The draft data of the {schemaName}."), ["dataDraft"] = SchemaBuilder.ObjectProperty(dataSchema, $"The draft data of the {schemaName}."),
["version"] = SchemaBuilder.NumberProperty($"The version of the {schemaName}.", true), ["version"] = SchemaBuilder.NumberProperty($"The version of the {schemaName}.", true),

4
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/JsonTypeVisitor.cs

@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
public JsonSchemaProperty? Visit(IField<AssetsFieldProperties> field) public JsonSchemaProperty? Visit(IField<AssetsFieldProperties> field)
{ {
var itemSchema = schemaResolver("AssetItem", SchemaBuilder.Guid()); var itemSchema = schemaResolver("AssetItem", SchemaBuilder.String());
return SchemaBuilder.ArrayProperty(itemSchema); return SchemaBuilder.ArrayProperty(itemSchema);
} }
@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
public JsonSchemaProperty? Visit(IField<ReferencesFieldProperties> field) public JsonSchemaProperty? Visit(IField<ReferencesFieldProperties> field)
{ {
var itemSchema = schemaResolver("ReferenceItem", SchemaBuilder.Guid()); var itemSchema = schemaResolver("ReferenceItem", SchemaBuilder.String());
return SchemaBuilder.ArrayProperty(itemSchema); return SchemaBuilder.ArrayProperty(itemSchema);
} }

10
backend/src/Squidex.Domain.Apps.Core.Operations/GenerateJsonSchema/SchemaBuilder.cs

@ -16,11 +16,6 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return new JsonSchema { Type = JsonObjectType.Object }; return new JsonSchema { Type = JsonObjectType.Object };
} }
public static JsonSchema Guid()
{
return new JsonSchema { Type = JsonObjectType.String, Format = JsonFormatStrings.Guid };
}
public static JsonSchema String() public static JsonSchema String()
{ {
return new JsonSchema { Type = JsonObjectType.String }; return new JsonSchema { Type = JsonObjectType.String };
@ -41,11 +36,6 @@ namespace Squidex.Domain.Apps.Core.GenerateJsonSchema
return Enrich(new JsonSchemaProperty { Type = JsonObjectType.String, Format = JsonFormatStrings.DateTime }, description, isRequired); return Enrich(new JsonSchemaProperty { Type = JsonObjectType.String, Format = JsonFormatStrings.DateTime }, description, isRequired);
} }
public static JsonSchemaProperty GuidProperty(string? description = null, bool isRequired = false)
{
return Enrich(new JsonSchemaProperty { Type = JsonObjectType.String, Format = JsonFormatStrings.Guid }, description, isRequired);
}
public static JsonSchemaProperty NumberProperty(string? description = null, bool isRequired = false) public static JsonSchemaProperty NumberProperty(string? description = null, bool isRequired = false)
{ {
return Enrich(new JsonSchemaProperty { Type = JsonObjectType.Number }, description, isRequired); return Enrich(new JsonSchemaProperty { Type = JsonObjectType.Number }, description, isRequired);

13
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs

@ -37,9 +37,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
{ {
if (context.GetValue("event")?.ToObjectValue() is EnrichedContentEvent contentEvent) if (context.GetValue("event")?.ToObjectValue() is EnrichedContentEvent contentEvent)
{ {
if (objectValue.ToObjectValue() is Guid guid && guid != Guid.Empty) if (objectValue.ToObjectValue() is DomainId id && id != DomainId.Empty)
{ {
var result = urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, guid); var result = urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, id);
return new StringValue(result); return new StringValue(result);
} }
@ -53,11 +53,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
{ {
if (input is ObjectValue objectValue) if (input is ObjectValue objectValue)
{ {
if (objectValue.ToObjectValue() is Guid guid && guid != Guid.Empty) if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent @event)
{ {
var result = urlGenerator.AssetContent(guid); if (objectValue.ToObjectValue() is DomainId id && id != DomainId.Empty)
{
var result = urlGenerator.AssetContent(@event.AppId, id);
return new StringValue(result); return new StringValue(result);
}
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
{ {
if (context.TryGetValue("event", out var temp) && temp is EnrichedAssetEvent assetEvent) if (context.TryGetValue("event", out var temp) && temp is EnrichedAssetEvent assetEvent)
{ {
return urlGenerator.AssetContent(assetEvent.Id); return urlGenerator.AssetContent(assetEvent.AppId, assetEvent.Id);
} }
return JsValue.Null; return JsValue.Null;

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

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
@ -23,6 +24,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
bool Trigger(EnrichedEvent @event, RuleTrigger trigger); bool Trigger(EnrichedEvent @event, RuleTrigger trigger);
bool Trigger(AppEvent @event, RuleTrigger trigger, Guid ruleId); bool Trigger(AppEvent @event, RuleTrigger trigger, DomainId ruleId);
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs

@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
if (@event is EnrichedAssetEvent assetEvent) if (@event is EnrichedAssetEvent assetEvent)
{ {
return urlGenerator.AssetContent(assetEvent.Id); return urlGenerator.AssetContent(assetEvent.AppId, assetEvent.Id);
} }
return null; return null;

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

@ -136,7 +136,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return CombineParts(text, parts); return CombineParts(text, parts);
} }
private string CombineParts(string text, List<TextPart> parts) private static string CombineParts(string text, List<TextPart> parts)
{ {
var span = text.AsSpan(); var span = text.AsSpan();
@ -224,7 +224,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return default; return default;
} }
private (bool IsNew, Match) Match(string test) private static (bool IsNew, Match) Match(string test)
{ {
var match = RegexPatternNew.Match(test); var match = RegexPatternNew.Match(test);

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

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.log = log; this.log = log;
} }
public virtual async Task<JobList> CreateJobsAsync(Rule rule, Guid ruleId, Envelope<IEvent> @event, bool ignoreStale = true) public virtual async Task<JobList> CreateJobsAsync(Rule rule, DomainId ruleId, Envelope<IEvent> @event, bool ignoreStale = true)
{ {
Guard.NotNull(rule, nameof(rule)); Guard.NotNull(rule, nameof(rule));
Guard.NotNull(@event, nameof(@event)); Guard.NotNull(@event, nameof(@event));
@ -139,7 +139,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var job = new RuleJob var job = new RuleJob
{ {
Id = Guid.NewGuid(), Id = DomainId.NewGuid(),
ActionData = string.Empty, ActionData = string.Empty,
ActionName = actionName, ActionName = actionName,
AppId = enrichedEvent.AppId.Id, AppId = enrichedEvent.AppId.Id,

5
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleTriggerHandler.cs

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
#pragma warning disable IDE0019 // Use pattern matching #pragma warning disable IDE0019 // Use pattern matching
@ -56,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return false; return false;
} }
bool IRuleTriggerHandler.Trigger(AppEvent @event, RuleTrigger trigger, Guid ruleId) bool IRuleTriggerHandler.Trigger(AppEvent @event, RuleTrigger trigger, DomainId ruleId)
{ {
if (@event is TEvent typed) if (@event is TEvent typed)
{ {
@ -73,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
protected abstract bool Trigger(TEnrichedEvent @event, TTrigger trigger); protected abstract bool Trigger(TEnrichedEvent @event, TTrigger trigger);
protected virtual bool Trigger(TEvent @event, TTrigger trigger, Guid ruleId) protected virtual bool Trigger(TEvent @event, TTrigger trigger, DomainId ruleId)
{ {
return true; return true;
} }

43
backend/src/Squidex.Domain.Apps.Core.Operations/IUrlGenerator.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -15,47 +14,47 @@ namespace Squidex.Domain.Apps.Core
{ {
bool CanGenerateAssetSourceUrl { get; } bool CanGenerateAssetSourceUrl { get; }
string? AssetSource(Guid assetId, long fileVersion); string? AssetSource(NamedId<DomainId> appId, DomainId assetId, long fileVersion);
string? AssetThumbnail(Guid assetId, AssetType assetType); string? AssetThumbnail(NamedId<DomainId> appId, DomainId assetId, AssetType assetType);
string AppSettingsUI(NamedId<Guid> appId); string AppSettingsUI(NamedId<DomainId> appId);
string AssetsUI(NamedId<Guid> appId); string AssetsUI(NamedId<DomainId> appId);
string AssetsUI(NamedId<Guid> appId, string? query = null); string AssetsUI(NamedId<DomainId> appId, string? query = null);
string AssetContent(Guid assetId); string AssetContent(NamedId<DomainId> appId, DomainId assetId);
string BackupsUI(NamedId<Guid> appId); string BackupsUI(NamedId<DomainId> appId);
string ClientsUI(NamedId<Guid> appId); string ClientsUI(NamedId<DomainId> appId);
string ContentsUI(NamedId<Guid> appId); string ContentsUI(NamedId<DomainId> appId);
string ContentsUI(NamedId<Guid> appId, NamedId<Guid> schemaId); string ContentsUI(NamedId<DomainId> appId, NamedId<DomainId> schemaId);
string ContentUI(NamedId<Guid> appId, NamedId<Guid> schemaId, Guid contentId); string ContentUI(NamedId<DomainId> appId, NamedId<DomainId> schemaId, DomainId contentId);
string ContributorsUI(NamedId<Guid> appId); string ContributorsUI(NamedId<DomainId> appId);
string DashboardUI(NamedId<Guid> appId); string DashboardUI(NamedId<DomainId> appId);
string LanguagesUI(NamedId<Guid> appId); string LanguagesUI(NamedId<DomainId> appId);
string PatternsUI(NamedId<Guid> appId); string PatternsUI(NamedId<DomainId> appId);
string PlansUI(NamedId<Guid> appId); string PlansUI(NamedId<DomainId> appId);
string RolesUI(NamedId<Guid> appId); string RolesUI(NamedId<DomainId> appId);
string RulesUI(NamedId<Guid> appId); string RulesUI(NamedId<DomainId> appId);
string SchemasUI(NamedId<Guid> appId); string SchemasUI(NamedId<DomainId> appId);
string SchemaUI(NamedId<Guid> appId, NamedId<Guid> schemaId); string SchemaUI(NamedId<DomainId> appId, NamedId<DomainId> schemaId);
string WorkflowsUI(NamedId<Guid> appId); string WorkflowsUI(NamedId<DomainId> appId);
string UI(); string UI();
} }

7
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs

@ -82,12 +82,9 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
public override void RemoveOwnProperty(string propertyName) public override void RemoveOwnProperty(string propertyName)
{ {
if (valuesToDelete == null) valuesToDelete ??= new HashSet<string>();
{
valuesToDelete = new HashSet<string>();
}
valuesToDelete.Add(propertyName); valuesToDelete.Add(propertyName);
valueProperties?.Remove(propertyName); valueProperties?.Remove(propertyName);
MarkChanged(); MarkChanged();

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

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

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptOperations.cs

@ -15,19 +15,19 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
private delegate void MessageDelegate(string? message); private delegate void MessageDelegate(string? message);
private static readonly MessageDelegate Disallow = new MessageDelegate(message => private static readonly MessageDelegate Disallow = message =>
{ {
message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed"; message = !string.IsNullOrWhiteSpace(message) ? message : "Not allowed";
throw new DomainForbiddenException(message); throw new DomainForbiddenException(message);
}); };
private static readonly MessageDelegate Reject = new MessageDelegate(message => private static readonly MessageDelegate Reject = message =>
{ {
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException("Script rejected the operation.", errors); throw new ValidationException("Script rejected the operation.", errors);
}); };
public static Engine AddDisallow(this Engine engine) public static Engine AddDisallow(this Engine engine)
{ {

9
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs

@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Claims; using System.Security.Claims;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Scripting namespace Squidex.Domain.Apps.Core.Scripting
{ {
@ -26,15 +27,15 @@ namespace Squidex.Domain.Apps.Core.Scripting
set => SetValue(value); set => SetValue(value);
} }
public Guid AppId public DomainId AppId
{ {
get => GetValue<Guid>(); get => GetValue<DomainId>();
set => SetValue(value); set => SetValue(value);
} }
public Guid ContentId public DomainId ContentId
{ {
get => GetValue<Guid>(); get => GetValue<DomainId>();
set => SetValue(value); set => SetValue(value);
} }

16
backend/src/Squidex.Domain.Apps.Core.Operations/Tags/ITagService.cs

@ -5,26 +5,26 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Tags namespace Squidex.Domain.Apps.Core.Tags
{ {
public interface ITagService public interface ITagService
{ {
Task<Dictionary<string, string>> GetTagIdsAsync(Guid appId, string group, HashSet<string> names); Task<Dictionary<string, string>> GetTagIdsAsync(DomainId appId, string group, HashSet<string> names);
Task<Dictionary<string, string>> NormalizeTagsAsync(Guid appId, string group, HashSet<string>? names, HashSet<string>? ids); Task<Dictionary<string, string>> NormalizeTagsAsync(DomainId appId, string group, HashSet<string>? names, HashSet<string>? ids);
Task<Dictionary<string, string>> DenormalizeTagsAsync(Guid appId, string group, HashSet<string> ids); Task<Dictionary<string, string>> DenormalizeTagsAsync(DomainId appId, string group, HashSet<string> ids);
Task<TagsSet> GetTagsAsync(Guid appId, string group); Task<TagsSet> GetTagsAsync(DomainId appId, string group);
Task<TagsExport> GetExportableTagsAsync(Guid appId, string group); Task<TagsExport> GetExportableTagsAsync(DomainId appId, string group);
Task RebuildTagsAsync(Guid appId, string group, TagsExport tags); Task RebuildTagsAsync(DomainId appId, string group, TagsExport tags);
Task ClearAsync(Guid appId, string group); Task ClearAsync(DomainId appId, string group);
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagGroups.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Tags namespace Squidex.Domain.Apps.Core.Tags
{ {
@ -13,7 +13,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
public const string Assets = "Assets"; public const string Assets = "Assets";
public static string Schemas(Guid schemaId) public static string Schemas(DomainId schemaId)
{ {
return $"Schemas_{schemaId}"; return $"Schemas_{schemaId}";
} }

5
backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -17,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
public static class TagNormalizer public static class TagNormalizer
{ {
public static async Task NormalizeAsync(this ITagService tagService, Guid appId, Guid schemaId, Schema schema, NamedContentData newData, NamedContentData? oldData) public static async Task NormalizeAsync(this ITagService tagService, DomainId appId, DomainId schemaId, Schema schema, NamedContentData newData, NamedContentData? oldData)
{ {
Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(tagService, nameof(tagService));
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -53,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Tags
} }
} }
public static async Task DenormalizeAsync(this ITagService tagService, Guid appId, Guid schemaId, Schema schema, params NamedContentData[] datas) public static async Task DenormalizeAsync(this ITagService tagService, DomainId appId, DomainId schemaId, Schema schema, params NamedContentData[] datas)
{ {
Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(tagService, nameof(tagService));
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));

1
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs

@ -45,6 +45,7 @@ namespace Squidex.Domain.Apps.Core.Templates
FluidValue.SetTypeMapping(type, x => new StringValue(x.ToString())); FluidValue.SetTypeMapping(type, x => new StringValue(x.ToString()));
} }
globalTypes.Register<NamedId<DomainId>>();
globalTypes.Register<NamedId<Guid>>(); globalTypes.Register<NamedId<Guid>>();
globalTypes.Register<NamedId<string>>(); globalTypes.Register<NamedId<string>>();
globalTypes.Register<NamedId<long>>(); globalTypes.Register<NamedId<long>>();

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -59,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (!field.Properties.AllowDuplicates) if (!field.Properties.AllowDuplicates)
{ {
yield return new UniqueValuesValidator<Guid>(); yield return new UniqueValuesValidator<string>();
} }
} }
@ -127,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (!field.Properties.AllowDuplicates) if (!field.Properties.AllowDuplicates)
{ {
yield return new UniqueValuesValidator<Guid>(); yield return new UniqueValuesValidator<string>();
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs

@ -5,14 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public interface IAssetInfo public interface IAssetInfo
{ {
Guid AssetId { get; } DomainId AssetId { get; }
long FileSize { get; } long FileSize { get; }

28
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -35,12 +35,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public (object? Result, JsonError? Error) Visit(IField<AssetsFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<AssetsFieldProperties> field)
{ {
return ConvertToGuidList(); return ConvertToStringList();
} }
public (object? Result, JsonError? Error) Visit(IField<ReferencesFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<ReferencesFieldProperties> field)
{ {
return ConvertToGuidList(); return ConvertToStringList();
} }
public (object? Result, JsonError? Error) Visit(IField<TagsFieldProperties> field) public (object? Result, JsonError? Error) Visit(IField<TagsFieldProperties> field)
@ -152,30 +152,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (value, null); return (value, null);
} }
private (object? Result, JsonError? Error) ConvertToGuidList()
{
if (value is JsonArray array)
{
var result = new List<Guid>(array.Count);
foreach (var item in array)
{
if (item is JsonString s && Guid.TryParse(s.Value, out var guid))
{
result.Add(guid);
}
else
{
return (null, new JsonError("Invalid json type, expected array of guid strings."));
}
}
return (result, null);
}
return (null, new JsonError("Invalid json type, expected array of guid strings."));
}
private (object? Result, JsonError? Error) ConvertToStringList() private (object? Result, JsonError? Error) ConvertToStringList()
{ {
if (value is JsonArray array) if (value is JsonArray array)

19
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -16,33 +15,33 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public ImmutableQueue<string> Path { get; } public ImmutableQueue<string> Path { get; }
public NamedId<Guid> AppId { get; } public NamedId<DomainId> AppId { get; }
public NamedId<Guid> SchemaId { get; } public NamedId<DomainId> SchemaId { get; }
public Schema Schema { get; } public Schema Schema { get; }
public Guid ContentId { get; } public DomainId ContentId { get; }
public bool IsOptional { get; } public bool IsOptional { get; }
public ValidationMode Mode { get; } public ValidationMode Mode { get; }
public ValidationContext( public ValidationContext(
NamedId<Guid> appId, NamedId<DomainId> appId,
NamedId<Guid> schemaId, NamedId<DomainId> schemaId,
Schema schema, Schema schema,
Guid contentId, DomainId contentId,
ValidationMode mode = ValidationMode.Default) ValidationMode mode = ValidationMode.Default)
: this(appId, schemaId, schema, contentId, ImmutableQueue<string>.Empty, false, mode) : this(appId, schemaId, schema, contentId, ImmutableQueue<string>.Empty, false, mode)
{ {
} }
private ValidationContext( private ValidationContext(
NamedId<Guid> appId, NamedId<DomainId> appId,
NamedId<Guid> schemaId, NamedId<DomainId> schemaId,
Schema schema, Schema schema,
Guid contentId, DomainId contentId,
ImmutableQueue<string> path, ImmutableQueue<string> path,
bool isOptional, bool isOptional,
ValidationMode mode = ValidationMode.Default) ValidationMode mode = ValidationMode.Default)

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids); public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<DomainId> ids);
public sealed class AssetsValidator : IValidator public sealed class AssetsValidator : IValidator
{ {
@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
return; return;
} }
if (value is ICollection<Guid> assetIds && assetIds.Count > 0) if (value is ICollection<DomainId> assetIds && assetIds.Count > 0)
{ {
var assets = await checkAssets(assetIds); var assets = await checkAssets(assetIds);
var index = 0; var index = 0;

11
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,14 +12,14 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public delegate Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> CheckContentsByIds(HashSet<Guid> ids); public delegate Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> CheckContentsByIds(HashSet<DomainId> ids);
public sealed class ReferencesValidator : IValidator public sealed class ReferencesValidator : IValidator
{ {
private readonly IEnumerable<Guid>? schemaIds; private readonly IEnumerable<DomainId>? schemaIds;
private readonly CheckContentsByIds checkReferences; private readonly CheckContentsByIds checkReferences;
public ReferencesValidator(IEnumerable<Guid>? schemaIds, CheckContentsByIds checkReferences) public ReferencesValidator(IEnumerable<DomainId>? schemaIds, CheckContentsByIds checkReferences)
{ {
Guard.NotNull(checkReferences, nameof(checkReferences)); Guard.NotNull(checkReferences, nameof(checkReferences));
@ -36,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
return; return;
} }
if (value is ICollection<Guid> contentIds) if (value is ICollection<DomainId> contentIds)
{ {
var foundIds = await checkReferences(contentIds.ToHashSet()); var foundIds = await checkReferences(contentIds.ToHashSet());
@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
var (schemaId, _) = foundIds.FirstOrDefault(x => x.Id == id); var (schemaId, _) = foundIds.FirstOrDefault(x => x.Id == id);
if (schemaId == Guid.Empty) if (schemaId == DomainId.Empty)
{ {
addError(context.Path, $"Contains invalid reference '{id}'."); addError(context.Path, $"Contains invalid reference '{id}'.");
} }

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public delegate Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> CheckUniqueness(FilterNode<ClrValue> filter); public delegate Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> CheckUniqueness(FilterNode<ClrValue> filter);
public sealed class UniqueValidator : IValidator public sealed class UniqueValidator : IValidator
{ {

31
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs

@ -5,9 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
@ -17,21 +15,27 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed class MongoAssetEntity : IAssetEntity, IVersionedEntity<Guid> public sealed class MongoAssetEntity : IAssetEntity, IVersionedEntity<DomainId>
{ {
[BsonId] [BsonId]
[BsonElement("_id")] [BsonElement("_id")]
[BsonRepresentation(BsonType.String)] public DomainId DocumentId { get; set; }
public Guid Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("_ai")] [BsonElement("_ai")]
[BsonRepresentation(BsonType.String)] public DomainId IndexedAppId { get; set; }
public Guid IndexedAppId { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("id")]
public DomainId Id { get; set; }
[BsonIgnoreIfDefault] [BsonIgnoreIfDefault]
[BsonElement("pi")] [BsonElement("pi")]
public Guid ParentId { get; set; } public DomainId ParentId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ct")] [BsonElement("ct")]
@ -41,10 +45,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement("mt")] [BsonElement("mt")]
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<Guid> AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("mm")] [BsonElement("mm")]
public string MimeType { get; set; } public string MimeType { get; set; }
@ -102,9 +102,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement("md")] [BsonElement("md")]
public AssetMetadata Metadata { get; set; } public AssetMetadata Metadata { get; set; }
public Guid AssetId public DomainId AssetId
{ {
get { return Id; } get { return Id; }
} }
public DomainId UniqueId
{
get { return DocumentId; }
}
} }
} }

30
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs

@ -5,30 +5,35 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed class MongoAssetFolderEntity : IAssetFolderEntity public sealed class MongoAssetFolderEntity : IAssetFolderEntity, IVersionedEntity<DomainId>
{ {
[BsonId] [BsonId]
[BsonElement("_id")] [BsonElement("_id")]
[BsonRepresentation(BsonType.String)] public DomainId DocumentId { get; set; }
public Guid Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("_ai")] [BsonElement("_ai")]
[BsonRepresentation(BsonType.String)] public DomainId IndexedAppId { get; set; }
public Guid IndexedAppId { get; set; }
[BsonRequired]
[BsonElement("id")]
public DomainId Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("pi")] [BsonElement("pi")]
public Guid ParentId { get; set; } public DomainId ParentId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ct")] [BsonElement("ct")]
@ -38,10 +43,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonElement("mt")] [BsonElement("mt")]
public Instant LastModified { get; set; } public Instant LastModified { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<Guid> AppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("fn")] [BsonElement("fn")]
public string FolderName { get; set; } public string FolderName { get; set; }
@ -61,5 +62,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
[BsonRequired] [BsonRequired]
[BsonElement("dl")] [BsonElement("dl")]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
public DomainId UniqueId
{
get { return DocumentId; }
}
} }
} }

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

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}, ct); }, ct);
} }
public async Task<IResultList<IAssetFolderEntity>> QueryAsync(Guid appId, Guid parentId) public async Task<IResultList<IAssetFolderEntity>> QueryAsync(DomainId appId, DomainId parentId)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>("QueryAsyncByQuery")) using (Profiler.TraceMethod<MongoAssetFolderRepository>("QueryAsyncByQuery"))
{ {
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
public async Task<IReadOnlyList<Guid>> QueryChildIdsAsync(Guid appId, Guid parentId) public async Task<IReadOnlyList<DomainId>> QueryChildIdsAsync(DomainId appId, DomainId parentId)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
@ -67,23 +67,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id) await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id)
.ToListAsync(); .ToListAsync();
return assetFolderEntities.Select(x => Guid.Parse(x[IdField.Value].AsString)).ToList(); return assetFolderEntities.Select(x => DomainId.Create(x[IdField.Value].AsString)).ToList();
} }
} }
public async Task<IAssetFolderEntity?> FindAssetFolderAsync(Guid id) public async Task<IAssetFolderEntity?> FindAssetFolderAsync(DomainId appId, DomainId id)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) using (Profiler.TraceMethod<MongoAssetFolderRepository>())
{ {
var documentId = DomainId.Combine(appId, id).ToString();
var assetFolderEntity = var assetFolderEntity =
await Collection.Find(x => x.Id == id) await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (assetFolderEntity?.IsDeleted == true)
{
return null;
}
return assetFolderEntity; return assetFolderEntity;
} }
} }

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

@ -19,14 +19,14 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderState, Guid> public sealed partial class MongoAssetFolderRepository : ISnapshotStore<AssetFolderState, DomainId>
{ {
async Task<(AssetFolderState Value, long Version)> ISnapshotStore<AssetFolderState, Guid>.ReadAsync(Guid key) async Task<(AssetFolderState Value, long Version)> ISnapshotStore<AssetFolderState, DomainId>.ReadAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) using (Profiler.TraceMethod<MongoAssetFolderRepository>())
{ {
var existing = var existing =
await Collection.Find(x => x.Id == key) await Collection.Find(x => x.DocumentId == key)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existing != null) if (existing != null)
@ -38,20 +38,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
async Task ISnapshotStore<AssetFolderState, Guid>.WriteAsync(Guid key, AssetFolderState value, long oldVersion, long newVersion) async Task ISnapshotStore<AssetFolderState, DomainId>.WriteAsync(DomainId key, AssetFolderState value, long oldVersion, long newVersion)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) using (Profiler.TraceMethod<MongoAssetFolderRepository>())
{ {
var entity = SimpleMapper.Map(value, new MongoAssetFolderEntity()); var entity = SimpleMapper.Map(value, new MongoAssetFolderEntity());
entity.Version = newVersion;
entity.IndexedAppId = value.AppId.Id; entity.IndexedAppId = value.AppId.Id;
await Collection.ReplaceOneAsync(x => x.Id == key && x.Version == oldVersion, entity, UpsertReplace); await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity);
} }
} }
async Task ISnapshotStore<AssetFolderState, Guid>.ReadAllAsync(Func<AssetFolderState, long, Task> callback, CancellationToken ct) async Task ISnapshotStore<AssetFolderState, DomainId>.ReadAllAsync(Func<AssetFolderState, long, Task> callback, CancellationToken ct)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) using (Profiler.TraceMethod<MongoAssetFolderRepository>())
{ {
@ -59,11 +58,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
async Task ISnapshotStore<AssetFolderState, Guid>.RemoveAsync(Guid key) async Task ISnapshotStore<AssetFolderState, DomainId>.RemoveAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoAssetFolderRepository>()) using (Profiler.TraceMethod<MongoAssetFolderRepository>())
{ {
await Collection.DeleteOneAsync(x => x.Id == key); await Collection.DeleteOneAsync(x => x.DocumentId == key);
} }
} }

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

@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}, ct); }, ct);
} }
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, Guid? parentId, ClrQuery query) public async Task<IResultList<IAssetEntity>> QueryAsync(DomainId appId, DomainId? parentId, ClrQuery query)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByQuery")) using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByQuery"))
{ {
@ -96,31 +96,31 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, HashSet<Guid> ids) public async Task<IReadOnlyList<DomainId>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByIds")) using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByIds"))
{ {
var assetEntities = var assetEntities =
await Collection.Find(BuildFilter(appId, ids)).Only(x => x.Id) await Collection.Find(BuildFilter(appId, ids)).Only(x => x.DocumentId)
.ToListAsync(); .ToListAsync();
return assetEntities.Select(x => Guid.Parse(x[IdField.Value].AsString)).ToList(); return assetEntities.Select(x => DomainId.Create(x[IdField.Value].AsString)).ToList();
} }
} }
public async Task<IReadOnlyList<Guid>> QueryChildIdsAsync(Guid appId, Guid parentId) public async Task<IReadOnlyList<DomainId>> QueryChildIdsAsync(DomainId appId, DomainId parentId)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
var assetEntities = var assetEntities =
await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id) await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.DocumentId)
.ToListAsync(); .ToListAsync();
return assetEntities.Select(x => Guid.Parse(x[IdField.Value].AsString)).ToList(); return assetEntities.Select(x => DomainId.Create(x[IdField.Value].AsString)).ToList();
} }
} }
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, HashSet<Guid> ids) public async Task<IResultList<IAssetEntity>> QueryAsync(DomainId appId, HashSet<DomainId> ids)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByIds")) using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByIds"))
{ {
@ -132,7 +132,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
public async Task<IAssetEntity?> FindAssetBySlugAsync(Guid appId, string slug) public async Task<IAssetEntity?> FindAssetBySlugAsync(DomainId appId, string slug)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
@ -144,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
public async Task<IReadOnlyList<IAssetEntity>> QueryByHashAsync(Guid appId, string hash) public async Task<IReadOnlyList<IAssetEntity>> QueryByHashAsync(DomainId appId, string hash)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
@ -156,7 +156,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
public async Task<IAssetEntity?> FindAssetAsync(Guid id) public async Task<IAssetEntity?> FindAssetAsync(DomainId appId, DomainId id)
{
using (Profiler.TraceMethod<MongoAssetRepository>())
{
var documentId = DomainId.Combine(appId, id).ToString();
var assetEntity =
await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted)
.FirstOrDefaultAsync();
return assetEntity;
}
}
public async Task<IAssetEntity?> FindAssetAsync(DomainId id)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
@ -168,11 +182,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
private static FilterDefinition<MongoAssetEntity> BuildFilter(Guid appId, HashSet<Guid> ids) private static FilterDefinition<MongoAssetEntity> BuildFilter(DomainId appId, HashSet<DomainId> ids)
{ {
var documentIds = ids.Select(x => DomainId.Combine(appId, x));
return Filter.And( return Filter.And(
Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.Id, documentIds),
Filter.In(x => x.Id, ids),
Filter.Ne(x => x.IsDeleted, true)); Filter.Ne(x => x.IsDeleted, true));
} }

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

@ -19,14 +19,14 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public sealed partial class MongoAssetRepository : ISnapshotStore<AssetState, Guid> public sealed partial class MongoAssetRepository : ISnapshotStore<AssetState, DomainId>
{ {
async Task<(AssetState Value, long Version)> ISnapshotStore<AssetState, Guid>.ReadAsync(Guid key) async Task<(AssetState Value, long Version)> ISnapshotStore<AssetState, DomainId>.ReadAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
var existing = var existing =
await Collection.Find(x => x.Id == key) await Collection.Find(x => x.DocumentId == key)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existing != null) if (existing != null)
@ -38,20 +38,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
async Task ISnapshotStore<AssetState, Guid>.WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion) async Task ISnapshotStore<AssetState, DomainId>.WriteAsync(DomainId key, AssetState value, long oldVersion, long newVersion)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
var entity = SimpleMapper.Map(value, new MongoAssetEntity()); var entity = SimpleMapper.Map(value, new MongoAssetEntity());
entity.Version = newVersion;
entity.IndexedAppId = value.AppId.Id; entity.IndexedAppId = value.AppId.Id;
await Collection.UpsertVersionedAsync(key, oldVersion, entity); await Collection.UpsertVersionedAsync(key, oldVersion, newVersion, entity);
} }
} }
async Task ISnapshotStore<AssetState, Guid>.ReadAllAsync(Func<AssetState, long, Task> callback, CancellationToken ct) async Task ISnapshotStore<AssetState, DomainId>.ReadAllAsync(Func<AssetState, long, Task> callback, CancellationToken ct)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
@ -59,11 +58,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
} }
} }
async Task ISnapshotStore<AssetState, Guid>.RemoveAsync(Guid key) async Task ISnapshotStore<AssetState, DomainId>.RemoveAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>()) using (Profiler.TraceMethod<MongoAssetRepository>())
{ {
await Collection.DeleteOneAsync(x => x.Id == key); await Collection.DeleteOneAsync(x => x.DocumentId == key);
} }
} }

8
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -5,11 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
return query; return query;
} }
public static FilterDefinition<MongoAssetEntity> BuildFilter(this ClrQuery query, Guid appId, Guid? parentId) public static FilterDefinition<MongoAssetEntity> BuildFilter(this ClrQuery query, DomainId appId, DomainId? parentId)
{ {
var filters = new List<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {
@ -46,12 +46,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
if (parentId.HasValue) if (parentId.HasValue)
{ {
if (parentId == Guid.Empty) if (parentId == DomainId.Empty)
{ {
filters.Add( filters.Add(
Filter.Or( Filter.Or(
Filter.Exists(x => x.ParentId, false), Filter.Exists(x => x.ParentId, false),
Filter.Eq(x => x.ParentId, Guid.Empty))); Filter.Eq(x => x.ParentId, DomainId.Empty)));
} }
else else
{ {

22
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionAll.cs

@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<DomainId> ids)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<Guid> ids) public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<DomainId> ids)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, Guid id) public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, DomainId id)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, HashSet<Guid> ids) public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -117,7 +117,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode) public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -125,19 +125,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task<MongoContentEntity> FindAsync(Guid id) public Task<MongoContentEntity> FindAsync(DomainId documentId)
{ {
return Collection.Find(x => x.Id == id).FirstOrDefaultAsync(); return Collection.Find(x => x.DocumentId == documentId).FirstOrDefaultAsync();
} }
public Task UpsertVersionedAsync(Guid id, long oldVersion, MongoContentEntity entity) public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity)
{ {
return Collection.UpsertVersionedAsync(id, oldVersion, entity); return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity);
} }
public Task RemoveAsync(Guid id) public Task RemoveAsync(DomainId documentId)
{ {
return Collection.DeleteOneAsync(x => x.Id == id); return Collection.DeleteOneAsync(x => x.DocumentId == documentId);
} }
} }
} }

16
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollectionPublished.cs

@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<DomainId> ids)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<Guid> ids) public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<DomainId> ids)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, Guid id) public async Task<IContentEntity?> FindContentAsync(ISchemaEntity schema, DomainId id)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, HashSet<Guid> ids) public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -113,14 +113,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task UpsertVersionedAsync(Guid id, long oldVersion, MongoContentEntity entity) public Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity entity)
{ {
return Collection.UpsertVersionedAsync(id, oldVersion, entity); return Collection.UpsertVersionedAsync(documentId, oldVersion, entity.Version, entity);
} }
public Task RemoveAsync(Guid id) public Task RemoveAsync(DomainId documentId)
{ {
return Collection.DeleteOneAsync(x => x.Id == id); return Collection.DeleteOneAsync(x => x.DocumentId == documentId);
} }
} }
} }

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

@ -5,9 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using MongoDB.Bson; using System.Linq;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -21,29 +20,37 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
[BsonIgnoreExtraElements] [BsonIgnoreExtraElements]
public sealed class MongoContentEntity : IContentEntity, IVersionedEntity<Guid> public sealed class MongoContentEntity : IContentEntity, IVersionedEntity<DomainId>
{ {
private NamedContentData data; private NamedContentData data;
[BsonId] [BsonId]
[BsonElement("_id")] [BsonElement("_id")]
[BsonRepresentation(BsonType.String)] public DomainId DocumentId { get; set; }
public Guid Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("_ai")] [BsonElement("_ai")]
[BsonRepresentation(BsonType.String)] public DomainId IndexedAppId { get; set; }
public Guid IndexedAppId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("_si")] [BsonElement("_si")]
[BsonRepresentation(BsonType.String)] public DomainId IndexedSchemaId { get; set; }
public Guid IndexedSchemaId { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<DomainId> AppId { get; set; }
[BsonRequired]
[BsonElement("si")]
public NamedId<DomainId> SchemaId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("rf")] [BsonElement("rf")]
[BsonRepresentation(BsonType.String)] public HashSet<DomainId>? ReferencedIds { get; set; }
public HashSet<Guid>? ReferencedIds { get; set; }
[BsonRequired]
[BsonElement("id")]
public DomainId Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ss")] [BsonElement("ss")]
@ -58,14 +65,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonJson] [BsonJson]
public IdContentData DataByIds { get; set; } public IdContentData DataByIds { get; set; }
[BsonRequired]
[BsonElement("ai")]
public NamedId<Guid> AppId { get; set; }
[BsonRequired]
[BsonElement("si")]
public NamedId<Guid> SchemaId { get; set; }
[BsonIgnoreIfNull] [BsonIgnoreIfNull]
[BsonElement("sa")] [BsonElement("sa")]
public Instant? ScheduledAt { get; set; } public Instant? ScheduledAt { get; set; }
@ -104,9 +103,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
get { return data; } get { return data; }
} }
public DomainId UniqueId
{
get { return DocumentId; }
}
public void LoadData(NamedContentData data, Schema schema, DataConverter converter) public void LoadData(NamedContentData data, Schema schema, DataConverter converter)
{ {
ReferencedIds = data.GetReferencedIds(schema); ReferencedIds = data.GetReferencedIds(schema).Select(x => DomainId.Combine(AppId, x)).ToHashSet();
DataByIds = converter.ToMongoModel(data, schema); DataByIds = converter.ToMongoModel(data, schema);
} }

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

@ -66,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids, SearchScope scope) public Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<DomainId> ids, SearchScope scope)
{ {
if (scope == SearchScope.All) if (scope == SearchScope.All)
{ {
@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<Guid> ids, SearchScope scope) public Task<List<(IContentEntity Content, ISchemaEntity Schema)>> QueryAsync(IAppEntity app, HashSet<DomainId> ids, SearchScope scope)
{ {
if (scope == SearchScope.All) if (scope == SearchScope.All)
{ {
@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task<IContentEntity?> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, SearchScope scope) public Task<IContentEntity?> FindContentAsync(IAppEntity app, ISchemaEntity schema, DomainId id, SearchScope scope)
{ {
if (scope == SearchScope.All) if (scope == SearchScope.All)
{ {
@ -102,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, HashSet<Guid> ids, SearchScope scope) public Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> QueryIdsAsync(DomainId appId, HashSet<DomainId> ids, SearchScope scope)
{ {
if (scope == SearchScope.All) if (scope == SearchScope.All)
{ {
@ -119,7 +119,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return collectionAll.QueryScheduledWithoutDataAsync(now, callback); return collectionAll.QueryScheduledWithoutDataAsync(now, callback);
} }
public Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode) public Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode)
{ {
return collectionAll.QueryIdsAsync(appId, schemaId, filterNode); return collectionAll.QueryIdsAsync(appId, schemaId, filterNode);
} }

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

@ -18,14 +18,14 @@ using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public partial class MongoContentRepository : ISnapshotStore<ContentState, Guid> public partial class MongoContentRepository : ISnapshotStore<ContentState, DomainId>
{ {
Task ISnapshotStore<ContentState, Guid>.ReadAllAsync(Func<ContentState, long, Task> callback, CancellationToken ct) Task ISnapshotStore<ContentState, DomainId>.ReadAllAsync(Func<ContentState, long, Task> callback, CancellationToken ct)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
async Task ISnapshotStore<ContentState, Guid>.ClearAsync() async Task ISnapshotStore<ContentState, DomainId>.ClearAsync()
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
async Task ISnapshotStore<ContentState, Guid>.RemoveAsync(Guid key) async Task ISnapshotStore<ContentState, DomainId>.RemoveAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
async Task<(ContentState Value, long Version)> ISnapshotStore<ContentState, Guid>.ReadAsync(Guid key) async Task<(ContentState Value, long Version)> ISnapshotStore<ContentState, DomainId>.ReadAsync(DomainId key)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
@ -62,11 +62,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
async Task ISnapshotStore<ContentState, Guid>.WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion) async Task ISnapshotStore<ContentState, DomainId>.WriteAsync(DomainId key, ContentState value, long oldVersion, long newVersion)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
if (value.SchemaId.Id == Guid.Empty) if (value.SchemaId.Id == DomainId.Empty)
{ {
return; return;
} }
@ -87,13 +87,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
else else
{ {
await DeletePublishedContentAsync(value.Id); await DeletePublishedContentAsync(value.AppId.Id, value.Id);
} }
} }
private Task DeletePublishedContentAsync(Guid key) private Task DeletePublishedContentAsync(DomainId appId, DomainId id)
{ {
return collectionPublished.RemoveAsync(key); var documentId = DomainId.Combine(appId, id).ToString();
return collectionPublished.RemoveAsync(documentId);
} }
private async Task UpsertDraftContentAsync(ContentState value, long oldVersion, long newVersion, ISchemaEntity schema) private async Task UpsertDraftContentAsync(ContentState value, long oldVersion, long newVersion, ISchemaEntity schema)
@ -105,34 +107,37 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
Version = newVersion Version = newVersion
}); });
content.DocumentId = value.UniqueId;
content.ScheduledAt = value.ScheduleJob?.DueTime; content.ScheduledAt = value.ScheduleJob?.DueTime;
content.ScheduleJob = value.ScheduleJob; content.ScheduleJob = value.ScheduleJob;
content.NewStatus = value.NewStatus; content.NewStatus = value.NewStatus;
content.LoadData(value.Data, schema.SchemaDef, converter); content.LoadData(value.Data, schema.SchemaDef, converter);
await collectionAll.UpsertVersionedAsync(content.Id, oldVersion, content); await collectionAll.UpsertVersionedAsync(content.DocumentId, oldVersion, content);
} }
private async Task UpsertPublishedContentAsync(ContentState value, long oldVersion, long newVersion, ISchemaEntity schema) private async Task UpsertPublishedContentAsync(ContentState value, long oldVersion, long newVersion, ISchemaEntity schema)
{ {
var content = SimpleMapper.Map(value, new MongoContentEntity var content = SimpleMapper.Map(value, new MongoContentEntity
{ {
Id = value.Id,
IndexedAppId = value.AppId.Id, IndexedAppId = value.AppId.Id,
IndexedSchemaId = value.SchemaId.Id, IndexedSchemaId = value.SchemaId.Id,
Version = newVersion Version = newVersion
}); });
content.DocumentId = value.UniqueId;
content.ScheduledAt = null; content.ScheduledAt = null;
content.ScheduleJob = null; content.ScheduleJob = null;
content.NewStatus = null; content.NewStatus = null;
content.LoadData(value.CurrentVersion.Data, schema.SchemaDef, converter); content.LoadData(value.CurrentVersion.Data, schema.SchemaDef, converter);
await collectionPublished.UpsertVersionedAsync(content.Id, oldVersion, content); await collectionPublished.UpsertVersionedAsync(content.DocumentId, oldVersion, content);
} }
private async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid schemaId) private async Task<ISchemaEntity> GetSchemaAsync(DomainId appId, DomainId schemaId)
{ {
var schema = await appProvider.GetSchemaAsync(appId, schemaId, true); var schema = await appProvider.GetSchemaAsync(appId, schemaId, true);

7
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContent.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
@ -23,11 +22,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
this.converter = converter; this.converter = converter;
} }
public async Task<IContentEntity?> DoAsync(ISchemaEntity schema, Guid id) public async Task<IContentEntity?> DoAsync(ISchemaEntity schema, DomainId id)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var find = Collection.Find(x => x.Id == id); var documentId = DomainId.Combine(schema.AppId, id).ToString();
var find = Collection.Find(x => x.DocumentId == documentId);
var contentEntity = await find.FirstOrDefaultAsync(); var contentEntity = await find.FirstOrDefaultAsync();

18
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByIds.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -28,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
this.appProvider = appProvider; this.appProvider = appProvider;
} }
public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> DoAsync(Guid appId, ISchemaEntity? schema, HashSet<Guid> ids) public async Task<List<(IContentEntity Content, ISchemaEntity Schema)>> DoAsync(DomainId appId, ISchemaEntity? schema, HashSet<DomainId> ids)
{ {
Guard.NotNull(ids, nameof(ids)); Guard.NotNull(ids, nameof(ids));
@ -52,9 +51,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
return result; return result;
} }
private async Task<IDictionary<Guid, ISchemaEntity>> GetSchemasAsync(Guid appId, ISchemaEntity? schema, List<MongoContentEntity> contentItems) private async Task<IDictionary<DomainId, ISchemaEntity>> GetSchemasAsync(DomainId appId, ISchemaEntity? schema, List<MongoContentEntity> contentItems)
{ {
var schemas = new Dictionary<Guid, ISchemaEntity>(); var schemas = new Dictionary<DomainId, ISchemaEntity>();
if (schema != null) if (schema != null)
{ {
@ -79,29 +78,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
return schemas; return schemas;
} }
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid appId, ICollection<Guid> ids) private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, ICollection<DomainId> ids)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.IndexedAppId, appId),
Filter.Ne(x => x.IsDeleted, true) Filter.Ne(x => x.IsDeleted, true)
}; };
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
{ {
var documentIds = ids.Select(x => DomainId.Combine(appId, x)).ToList();
if (ids.Count > 1) if (ids.Count > 1)
{ {
filters.Add( filters.Add(
Filter.Or( Filter.Or(
Filter.In(x => x.Id, ids))); Filter.In(x => x.DocumentId, documentIds)));
} }
else else
{ {
var first = ids.First(); var first = documentIds.First();
filters.Add( filters.Add(
Filter.Or( Filter.Or(
Filter.Eq(x => x.Id, first))); Filter.Eq(x => x.DocumentId, first)));
} }
} }

20
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@ -34,8 +33,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
[BsonId] [BsonId]
[BsonElement("_id")] [BsonElement("_id")]
[BsonRepresentation(BsonType.String)] public DomainId Id { get; set; }
public Guid Id { get; set; }
public MongoContentEntity[] Joined { get; set; } public MongoContentEntity[] Joined { get; set; }
} }
@ -51,6 +49,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
var index = var index =
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted) .Ascending(x => x.IsDeleted)
.Ascending(x => x.ReferencedIds) .Ascending(x => x.ReferencedIds)
@ -69,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
query = query.AdjustToModel(schema.SchemaDef); query = query.AdjustToModel(schema.SchemaDef);
List<Guid>? fullTextIds = null; List<DomainId>? fullTextIds = null;
if (!string.IsNullOrWhiteSpace(query.FullText)) if (!string.IsNullOrWhiteSpace(query.FullText))
{ {
@ -83,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
} }
} }
var filter = CreateFilter(schema.Id, fullTextIds, query); var filter = CreateFilter(schema.AppId.Id, schema.Id, fullTextIds, query);
var contentCount = Collection.Find(filter).CountDocumentsAsync(); var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems = FindContentsAsync(query, filter); var contentItems = FindContentsAsync(query, filter);
@ -125,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
.QuerySort(query) .QuerySort(query)
.QuerySkip(query) .QuerySkip(query)
.QueryLimit(query) .QueryLimit(query)
.Lookup<IdOnly, MongoContentEntity, IdOnly>(Collection, x => x.Id, x => x.Id, x => x.Joined) .Lookup<IdOnly, MongoContentEntity, IdOnly>(Collection, x => x.Id, x => x.DocumentId, x => x.Joined)
.ToListAsync(); .ToListAsync();
return joined.Select(x => x.Joined[0]).ToList(); return joined.Select(x => x.Joined[0]).ToList();
@ -146,20 +145,23 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
return query.Sort?.All(x => x.Path.ToString() == "mt" && x.Order == SortOrder.Descending) == true; return query.Sort?.All(x => x.Path.ToString() == "mt" && x.Order == SortOrder.Descending) == true;
} }
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid schemaId, ICollection<Guid>? ids, ClrQuery? query) private static FilterDefinition<MongoContentEntity> CreateFilter(DomainId appId, DomainId schemaId, ICollection<DomainId>? ids, ClrQuery? query)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.IndexedAppId, appId),
Filter.Eq(x => x.IndexedSchemaId, schemaId), Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true) Filter.Ne(x => x.IsDeleted, true)
}; };
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
{ {
var documentIds = ids.Select(x => DomainId.Combine(appId, x)).ToList();
filters.Add( filters.Add(
Filter.Or( Filter.Or(
Filter.AnyIn(x => x.ReferencedIds, ids), Filter.AnyIn(x => x.ReferencedIds, documentIds),
Filter.In(x => x.Id, ids))); Filter.In(x => x.DocumentId, documentIds)));
} }
if (query?.Filter != null) if (query?.Filter != null)

24
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryIdsAsync.cs

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -20,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
internal sealed class QueryIdsAsync : OperationBase internal sealed class QueryIdsAsync : OperationBase
{ {
private static readonly List<(Guid SchemaId, Guid Id)> EmptyIds = new List<(Guid SchemaId, Guid Id)>(); private static readonly IReadOnlyList<(DomainId SchemaId, DomainId Id)> EmptyIds = new List<(DomainId SchemaId, DomainId Id)>();
private static readonly Lazy<string> IdField = new Lazy<string>(GetIdField); private static readonly Lazy<string> IdField = new Lazy<string>(GetIdField);
private static readonly Lazy<string> SchemaIdField = new Lazy<string>(GetSchemaIdField); private static readonly Lazy<string> SchemaIdField = new Lazy<string>(GetSchemaIdField);
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
@ -34,28 +35,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{ {
var index = var index =
new CreateIndexModel<MongoContentEntity>(Index new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)); .Ascending(x => x.IsDeleted));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct); return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, HashSet<Guid> ids) public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> DoAsync(DomainId appId, HashSet<DomainId> ids)
{ {
var documentIds = ids.Select(x => DomainId.Combine(appId, x));
var filter = var filter =
Filter.And( Filter.And(
Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.DocumentId, documentIds),
Filter.In(x => x.Id, ids),
Filter.Ne(x => x.IsDeleted, true)); Filter.Ne(x => x.IsDeleted, true));
var contentEntities = var contentEntities =
await Collection.Find(filter).Only(x => x.Id, x => x.IndexedSchemaId) await Collection.Find(filter).Only(x => x.Id, x => x.IndexedSchemaId)
.ToListAsync(); .ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x[SchemaIdField.Value].AsString), Guid.Parse(x[IdField.Value].AsString))).ToList(); return contentEntities.Select(x => (DomainId.Create(x[SchemaIdField.Value].AsString), DomainId.Create(x[IdField.Value].AsString))).ToList();
} }
public async Task<IReadOnlyList<(Guid SchemaId, Guid Id)>> DoAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode) public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id)>> DoAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode)
{ {
var schema = await appProvider.GetSchemaAsync(appId, schemaId); var schema = await appProvider.GetSchemaAsync(appId, schemaId);
@ -64,19 +67,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
return EmptyIds; return EmptyIds;
} }
var filter = BuildFilter(filterNode.AdjustToModel(schema.SchemaDef), schemaId); var filter = BuildFilter(filterNode.AdjustToModel(schema.SchemaDef), appId, schemaId);
var contentEntities = var contentEntities =
await Collection.Find(filter).Only(x => x.Id, x => x.IndexedSchemaId) await Collection.Find(filter).Only(x => x.DocumentId, x => x.IndexedSchemaId)
.ToListAsync(); .ToListAsync();
return contentEntities.Select(x => (Guid.Parse(x[SchemaIdField.Value].AsString), Guid.Parse(x[IdField.Value].AsString))).ToList(); return contentEntities.Select(x => (DomainId.Create(x[SchemaIdField.Value].AsString), DomainId.Create(x[IdField.Value].AsString))).ToList();
} }
public static FilterDefinition<MongoContentEntity> BuildFilter(FilterNode<ClrValue>? filterNode, Guid schemaId) public static FilterDefinition<MongoContentEntity> BuildFilter(FilterNode<ClrValue>? filterNode, DomainId appId, DomainId schemaId)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.IndexedAppId, appId),
Filter.Eq(x => x.IndexedSchemaId, schemaId), Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true) Filter.Ne(x => x.IsDeleted, true)
}; };

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Threading; using System.Threading;
using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.Serialization.Serializers;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -14,13 +15,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public sealed class StatusSerializer : SerializerBase<Status> public sealed class StatusSerializer : SerializerBase<Status>
{ {
private static volatile int isRegistered; private static int isRegistered;
public static void Register() public static void Register()
{ {
if (Interlocked.Increment(ref isRegistered) == 1) if (Interlocked.Increment(ref isRegistered) == 1)
{ {
BsonSerializer.RegisterSerializer(new StatusSerializer()); try
{
BsonSerializer.RegisterSerializer(new StatusSerializer());
}
catch (BsonSerializationException)
{
return;
}
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoIndexStorage.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
this.bucket = bucket; this.bucket = bucket;
} }
public async Task<LuceneDirectory> CreateDirectoryAsync(Guid ownerId) public async Task<LuceneDirectory> CreateDirectoryAsync(DomainId ownerId)
{ {
var fileId = $"index_{ownerId}"; var fileId = $"index_{ownerId}";

61
backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndexState.cs

@ -0,0 +1,61 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Entities.Contents.Text.State;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
{
public sealed class MongoTextIndexState
{
[BsonId]
[BsonElement]
public DomainId DocumentId { get; set; }
[BsonRequired]
[BsonElement]
public DomainId ContentId { get; set; }
[BsonRequired]
[BsonElement("c")]
public string DocIdCurrent { get; set; }
[BsonRequired]
[BsonElement("n")]
public string? DocIdNew { get; set; }
[BsonRequired]
[BsonElement("p")]
public string? DocIdForPublished { get; set; }
public MongoTextIndexState()
{
}
public MongoTextIndexState(DomainId documentId, TextContentState state)
{
DocumentId = documentId;
ContentId = state.ContentId;
DocIdNew = state.DocIdNew;
DocIdCurrent = state.DocIdCurrent;
DocIdForPublished = state.DocIdForPublished;
}
public TextContentState ToState()
{
return new TextContentState
{
ContentId = ContentId,
DocIdNew = DocIdNew,
DocIdCurrent = DocIdCurrent,
DocIdForPublished = DocIdForPublished
};
}
}
}

43
backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndexerState.cs

@ -5,34 +5,16 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson.Serialization;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Domain.Apps.Entities.Contents.Text.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.FullText namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
{ {
public sealed class MongoTextIndexerState : MongoRepositoryBase<TextContentState>, ITextIndexerState public sealed class MongoTextIndexerState : MongoRepositoryBase<MongoTextIndexState>, ITextIndexerState
{ {
static MongoTextIndexerState()
{
BsonClassMap.RegisterClassMap<TextContentState>(cm =>
{
cm.MapIdField(x => x.ContentId);
cm.MapProperty(x => x.DocIdCurrent)
.SetElementName("c");
cm.MapProperty(x => x.DocIdNew)
.SetElementName("n").SetIgnoreIfNull(true);
cm.MapProperty(x => x.DocIdForPublished)
.SetElementName("p").SetIgnoreIfNull(true);
});
}
public MongoTextIndexerState(IMongoDatabase database, bool setup = false) public MongoTextIndexerState(IMongoDatabase database, bool setup = false)
: base(database, setup) : base(database, setup)
{ {
@ -43,19 +25,28 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
return "TextIndexerState"; return "TextIndexerState";
} }
public Task<TextContentState?> GetAsync(Guid contentId) public async Task<TextContentState?> GetAsync(DomainId appId, DomainId contentId)
{ {
return Collection.Find(x => x.ContentId == contentId).FirstOrDefaultAsync()!; var documentId = DomainId.Combine(appId, contentId).ToString();
var result = await Collection.Find(x => x.DocumentId == documentId).FirstOrDefaultAsync()!;
return result?.ToState();
} }
public Task RemoveAsync(Guid contentId) public Task RemoveAsync(DomainId appId, DomainId contentId)
{ {
return Collection.DeleteOneAsync(x => x.ContentId == contentId); var documentId = DomainId.Combine(appId, contentId).ToString();
return Collection.DeleteOneAsync(x => x.DocumentId == documentId);
} }
public Task SetAsync(TextContentState state) public Task SetAsync(DomainId appId, TextContentState state)
{ {
return Collection.ReplaceOneAsync(x => x.ContentId == state.ContentId, state, UpsertReplace); var documentId = DomainId.Combine(appId, state.ContentId).ToString();
var document = new MongoTextIndexState(documentId, state);
return Collection.ReplaceOneAsync(x => x.DocumentId == documentId, document, UpsertReplace);
} }
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,6 +12,7 @@ using MongoDB.Bson.Serialization;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Domain.Apps.Entities.History.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.History namespace Squidex.Domain.Apps.Entities.MongoDb.History
@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
cancellationToken: ct); cancellationToken: ct);
} }
public async Task<IReadOnlyList<HistoryEvent>> QueryByChannelAsync(Guid appId, string channelPrefix, int count) public async Task<IReadOnlyList<HistoryEvent>> QueryByChannelAsync(DomainId appId, string channelPrefix, int count)
{ {
if (!string.IsNullOrWhiteSpace(channelPrefix)) if (!string.IsNullOrWhiteSpace(channelPrefix))
{ {
@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.History
return Collection.ReplaceOneAsync(x => x.Id == item.Id, item, UpsertReplace); return Collection.ReplaceOneAsync(x => x.Id == item.Id, item, UpsertReplace);
} }
public Task RemoveAsync(Guid appId) public Task RemoveAsync(DomainId appId)
{ {
return Collection.DeleteManyAsync(x => x.AppId == appId); return Collection.DeleteManyAsync(x => x.AppId == appId);
} }

18
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
@ -20,13 +20,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
[BsonRepresentation(BsonType.String)] public DomainId AppId { get; set; }
public Guid AppId { get; set; }
[BsonIgnoreIfDefault] [BsonIgnoreIfDefault]
[BsonElement] [BsonElement]
[BsonRepresentation(BsonType.String)] public DomainId RuleId { get; set; }
public Guid RuleId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
@ -58,5 +56,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Instant? NextAttempt { get; set; } public Instant? NextAttempt { get; set; }
DomainId IEntity.Id
{
get { return DocumentId; }
}
DomainId IEntity.UniqueId
{
get { return DocumentId; }
}
} }
} }

32
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -43,8 +43,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
await collection.Indexes.CreateManyAsync(new[] await collection.Indexes.CreateManyAsync(new[]
{ {
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.NextAttempt)), new CreateIndexModel<MongoRuleEventEntity>(
new CreateIndexModel<MongoRuleEventEntity>(Index.Ascending(x => x.AppId).Descending(x => x.Created)), Index.Ascending(x => x.NextAttempt)),
new CreateIndexModel<MongoRuleEventEntity>(
Index.Ascending(x => x.AppId).Descending(x => x.Created)),
new CreateIndexModel<MongoRuleEventEntity>( new CreateIndexModel<MongoRuleEventEntity>(
Index Index
.Ascending(x => x.Expires), .Ascending(x => x.Expires),
@ -60,13 +64,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return Collection.Find(x => x.NextAttempt < now).ForEachAsync(callback, ct); return Collection.Find(x => x.NextAttempt < now).ForEachAsync(callback, ct);
} }
public async Task<IResultList<IRuleEventEntity>> QueryByAppAsync(Guid appId, Guid? ruleId = null, int skip = 0, int take = 20) public async Task<IResultList<IRuleEventEntity>> QueryByAppAsync(DomainId appId, DomainId? ruleId = null, int skip = 0, int take = 20)
{ {
var filter = Filter.Eq(x => x.AppId, appId); var filter = Filter.Eq(x => x.AppId, appId);
if (ruleId.HasValue) if (ruleId.HasValue && ruleId.Value != DomainId.Empty)
{ {
filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId)); filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId.Value));
} }
var taskForItems = Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(); var taskForItems = Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync();
@ -77,18 +81,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return ResultList.Create(total, items); return ResultList.Create(total, items);
} }
public async Task<IRuleEventEntity> FindAsync(Guid id) public async Task<IRuleEventEntity> FindAsync(DomainId id)
{ {
var ruleEvent = var ruleEvent =
await Collection.Find(x => x.Id == id) await Collection.Find(x => x.DocumentId == id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
return ruleEvent; return ruleEvent;
} }
public Task EnqueueAsync(Guid id, Instant nextAttempt) public Task EnqueueAsync(DomainId id, Instant nextAttempt)
{ {
return Collection.UpdateOneAsync(x => x.Id == id, Update.Set(x => x.NextAttempt, nextAttempt)); return Collection.UpdateOneAsync(x => x.DocumentId == id, Update.Set(x => x.NextAttempt, nextAttempt));
} }
public async Task EnqueueAsync(RuleJob job, Instant? nextAttempt, CancellationToken ct = default) public async Task EnqueueAsync(RuleJob job, Instant? nextAttempt, CancellationToken ct = default)
@ -98,9 +102,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
await Collection.InsertOneIfNotExistsAsync(entity, ct); await Collection.InsertOneIfNotExistsAsync(entity, ct);
} }
public Task CancelAsync(Guid id) public Task CancelAsync(DomainId id)
{ {
return Collection.UpdateOneAsync(x => x.Id == id, return Collection.UpdateOneAsync(x => x.DocumentId == id,
Update Update
.Set(x => x.NextAttempt, null) .Set(x => x.NextAttempt, null)
.Set(x => x.JobResult, RuleJobResult.Cancelled)); .Set(x => x.JobResult, RuleJobResult.Cancelled));
@ -120,7 +124,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
await statisticsCollection.IncrementFailed(job.AppId, job.RuleId, update.Finished); await statisticsCollection.IncrementFailed(job.AppId, job.RuleId, update.Finished);
} }
await Collection.UpdateOneAsync(x => x.Id == job.Id, var documentId = job.Id.ToString();
await Collection.UpdateOneAsync(x => x.DocumentId == documentId,
Update Update
.Set(x => x.Result, update.ExecutionResult) .Set(x => x.Result, update.ExecutionResult)
.Set(x => x.LastDump, update.ExecutionDump) .Set(x => x.LastDump, update.ExecutionDump)
@ -129,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
.Inc(x => x.NumCalls, 1)); .Inc(x => x.NumCalls, 1));
} }
public Task<IReadOnlyList<RuleStatistics>> QueryStatisticsByAppAsync(Guid appId) public Task<IReadOnlyList<RuleStatistics>> QueryStatisticsByAppAsync(DomainId appId)
{ {
return statisticsCollection.QueryByAppAsync(appId); return statisticsCollection.QueryByAppAsync(appId);
} }

15
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleStatisticsCollection.cs

@ -5,16 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Rules namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
@ -23,15 +21,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
static MongoRuleStatisticsCollection() static MongoRuleStatisticsCollection()
{ {
var guidSerializer = new GuidSerializer().WithRepresentation(BsonType.String);
BsonClassMap.RegisterClassMap<RuleStatistics>(cm => BsonClassMap.RegisterClassMap<RuleStatistics>(cm =>
{ {
cm.AutoMap(); cm.AutoMap();
cm.MapProperty(x => x.AppId).SetSerializer(guidSerializer);
cm.MapProperty(x => x.RuleId).SetSerializer(guidSerializer);
cm.SetIgnoreExtraElements(true); cm.SetIgnoreExtraElements(true);
}); });
} }
@ -56,14 +49,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
cancellationToken: ct); cancellationToken: ct);
} }
public async Task<IReadOnlyList<RuleStatistics>> QueryByAppAsync(Guid appId) public async Task<IReadOnlyList<RuleStatistics>> QueryByAppAsync(DomainId appId)
{ {
var statistics = await Collection.Find(x => x.AppId == appId).ToListAsync(); var statistics = await Collection.Find(x => x.AppId == appId).ToListAsync();
return statistics; return statistics;
} }
public Task IncrementSuccess(Guid appId, Guid ruleId, Instant now) public Task IncrementSuccess(DomainId appId, DomainId ruleId, Instant now)
{ {
return Collection.UpdateOneAsync( return Collection.UpdateOneAsync(
x => x.AppId == appId && x.RuleId == ruleId, x => x.AppId == appId && x.RuleId == ruleId,
@ -75,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
Upsert); Upsert);
} }
public Task IncrementFailed(Guid appId, Guid ruleId, Instant now) public Task IncrementFailed(DomainId appId, DomainId ruleId, Instant now)
{ {
return Collection.UpdateOneAsync( return Collection.UpdateOneAsync(
x => x.AppId == appId && x.RuleId == ruleId, x => x.AppId == appId && x.RuleId == ruleId,

15
backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
@ -40,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities
this.indexSchemas = indexSchemas; this.indexSchemas = indexSchemas;
} }
public Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaAsync(Guid appId, Guid id) public Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaAsync(DomainId appId, DomainId id)
{ {
return localCache.GetOrCreateAsync($"GetAppWithSchemaAsync({appId}, {id})", async () => return localCache.GetOrCreateAsync($"GetAppWithSchemaAsync({appId}, {id})", async () =>
{ {
@ -48,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities
}); });
} }
private async Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaUncachedAsync(Guid appId, Guid id) private async Task<(IAppEntity?, ISchemaEntity?)> GetAppWithSchemaUncachedAsync(DomainId appId, DomainId id)
{ {
var app = await GetAppAsync(appId); var app = await GetAppAsync(appId);
@ -67,7 +66,7 @@ namespace Squidex.Domain.Apps.Entities
return (app, schema); return (app, schema);
} }
public Task<IAppEntity?> GetAppAsync(Guid appId) public Task<IAppEntity?> GetAppAsync(DomainId appId)
{ {
return localCache.GetOrCreateAsync($"GetAppAsync({appId})", async () => return localCache.GetOrCreateAsync($"GetAppAsync({appId})", async () =>
{ {
@ -91,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities
}); });
} }
public Task<ISchemaEntity?> GetSchemaAsync(Guid appId, string name) public Task<ISchemaEntity?> GetSchemaAsync(DomainId appId, string name)
{ {
return localCache.GetOrCreateAsync($"GetSchemaAsync({appId}, {name})", async () => return localCache.GetOrCreateAsync($"GetSchemaAsync({appId}, {name})", async () =>
{ {
@ -99,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities
}); });
} }
public Task<ISchemaEntity?> GetSchemaAsync(Guid appId, Guid id, bool allowDeleted = false) public Task<ISchemaEntity?> GetSchemaAsync(DomainId appId, DomainId id, bool allowDeleted = false)
{ {
return localCache.GetOrCreateAsync($"GetSchemaAsync({appId}, {id}, {allowDeleted})", async () => return localCache.GetOrCreateAsync($"GetSchemaAsync({appId}, {id}, {allowDeleted})", async () =>
{ {
@ -107,7 +106,7 @@ namespace Squidex.Domain.Apps.Entities
}); });
} }
public Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId) public Task<List<ISchemaEntity>> GetSchemasAsync(DomainId appId)
{ {
return localCache.GetOrCreateAsync($"GetSchemasAsync({appId})", async () => return localCache.GetOrCreateAsync($"GetSchemasAsync({appId})", async () =>
{ {
@ -115,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities
}); });
} }
public Task<List<IRuleEntity>> GetRulesAsync(Guid appId) public Task<List<IRuleEntity>> GetRulesAsync(DomainId appId)
{ {
return localCache.GetOrCreateAsync($"GetRulesAsync({appId})", async () => return localCache.GetOrCreateAsync($"GetRulesAsync({appId})", async () =>
{ {

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -70,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
using (var uploadStream = file.OpenRead()) using (var uploadStream = file.OpenRead())
{ {
await appImageStore.UploadAsync(uploadImage.AppId, uploadStream); await appImageStore.UploadAsync(uploadImage.AppId.Id, uploadStream);
} }
} }
} }

35
backend/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs

@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppDomainObject( public AppDomainObject(
InitialPatterns initialPatterns, InitialPatterns initialPatterns,
IStore<Guid> store, IStore<DomainId> store,
ISemanticLog log, ISemanticLog log,
IAppPlansProvider appPlansProvider, IAppPlansProvider appPlansProvider,
IAppPlanBillingManager appPlansBillingManager, IAppPlanBillingManager appPlansBillingManager,
@ -52,10 +52,24 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.initialPatterns = initialPatterns; this.initialPatterns = initialPatterns;
} }
public override Task<object?> ExecuteAsync(IAggregateCommand command) protected override bool IsDeleted()
{ {
VerifyNotArchived(); return Snapshot.IsArchived;
}
protected override bool CanAcceptCreation(ICommand command)
{
return command is CreateApp;
}
protected override bool CanAccept(ICommand command)
{
return command is AppUpdateCommand update &&
Equals(update?.AppId?.Id, Snapshot.Id);
}
public override Task<object?> ExecuteAsync(IAggregateCommand command)
{
switch (command) switch (command)
{ {
case CreateApp createApp: case CreateApp createApp:
@ -466,20 +480,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
RaiseEvent(SimpleMapper.Map(command, new AppArchived())); RaiseEvent(SimpleMapper.Map(command, new AppArchived()));
} }
private void VerifyNotArchived()
{
if (Snapshot.IsArchived)
{
throw new DomainException("App has already been archived.");
}
}
private void RaiseEvent(AppEvent @event) private void RaiseEvent(AppEvent @event)
{ {
if (@event.AppId == null) @event.AppId ??= Snapshot.NamedId();
{
@event.AppId = NamedId.Of(Snapshot.Id, Snapshot.Name);
}
RaiseEvent(Envelope.Create(@event)); RaiseEvent(Envelope.Create(@event));
} }
@ -489,7 +492,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return new AppCreated { Name = name }; return new AppCreated { Name = name };
} }
private static AppPatternAdded CreateInitialPattern(Guid id, AppPattern pattern) private static AppPatternAdded CreateInitialPattern(DomainId id, AppPattern pattern)
{ {
return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message }; return new AppPatternAdded { PatternId = id, Name = pattern.Name, Pattern = pattern.Pattern, Message = pattern.Message };
} }

5
backend/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs

@ -5,16 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public static class AppExtensions public static class AppExtensions
{ {
public static NamedId<Guid> NamedId(this IAppEntity app) public static NamedId<DomainId> NamedId(this IAppEntity app)
{ {
return new NamedId<Guid>(app.Id, app.Name); return new NamedId<DomainId>(app.Id, app.Name);
} }
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
@ -133,7 +132,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return ForEvent(e, "settings.roles").Param("Name", name); return ForEvent(e, "settings.roles").Param("Name", name);
} }
private HistoryEvent CreatePatternsEvent(IEvent e, Guid id, string? name = null) private HistoryEvent CreatePatternsEvent(IEvent e, DomainId id, string? name = null)
{ {
return ForEvent(e, "settings.patterns").Param("PatternId", id).Param("Name", name); return ForEvent(e, "settings.patterns").Param("PatternId", id).Param("Name", name);
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
var appId = context.App.NamedId(); var appId = context.App.NamedId();
void Search(string term, string permissionId, Func<NamedId<Guid>, string> generate, SearchResultType type) void Search(string term, string permissionId, Func<NamedId<DomainId>, string> generate, SearchResultType type)
{ {
if (result.Count < MaxItems && term.Contains(query, StringComparison.OrdinalIgnoreCase)) if (result.Count < MaxItems && term.Contains(query, StringComparison.OrdinalIgnoreCase))
{ {

13
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans; using Orleans;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -25,34 +24,34 @@ namespace Squidex.Domain.Apps.Entities.Apps
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
} }
public async Task<JsonObject> GetAsync(Guid appId, string? userId) public async Task<JsonObject> GetAsync(DomainId appId, string? userId)
{ {
var result = await GetGrain(appId, userId).GetAsync(); var result = await GetGrain(appId, userId).GetAsync();
return result.Value; return result.Value;
} }
public Task RemoveAsync(Guid appId, string? userId, string path) public Task RemoveAsync(DomainId appId, string? userId, string path)
{ {
return GetGrain(appId, userId).RemoveAsync(path); return GetGrain(appId, userId).RemoveAsync(path);
} }
public Task SetAsync(Guid appId, string? userId, string path, IJsonValue value) public Task SetAsync(DomainId appId, string? userId, string path, IJsonValue value)
{ {
return GetGrain(appId, userId).SetAsync(path, value.AsJ()); return GetGrain(appId, userId).SetAsync(path, value.AsJ());
} }
public Task SetAsync(Guid appId, string? userId, JsonObject settings) public Task SetAsync(DomainId appId, string? userId, JsonObject settings)
{ {
return GetGrain(appId, userId).SetAsync(settings.AsJ()); return GetGrain(appId, userId).SetAsync(settings.AsJ());
} }
private IAppUISettingsGrain GetGrain(Guid appId, string? userId) private IAppUISettingsGrain GetGrain(DomainId appId, string? userId)
{ {
return grainFactory.GetGrain<IAppUISettingsGrain>(GetKey(appId, userId)); return grainFactory.GetGrain<IAppUISettingsGrain>(GetKey(appId, userId));
} }
private static string GetKey(Guid appId, string? userId) private static string GetKey(DomainId appId, string? userId)
{ {
if (!string.IsNullOrWhiteSpace(userId)) if (!string.IsNullOrWhiteSpace(userId))
{ {

9
backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -115,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
await appUISettings.SetAsync(context.AppId, null, json); await appUISettings.SetAsync(context.AppId, null, json);
} }
private async Task ReserveAppAsync(Guid appId, string appName) private async Task ReserveAppAsync(DomainId appId, string appName)
{ {
appReservation = await appsIndex.ReserveAsync(appId, appName); appReservation = await appsIndex.ReserveAsync(appId, appName);
@ -125,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
} }
} }
public async Task CleanupRestoreErrorAsync(Guid appId) public async Task CleanupRestoreErrorAsync(DomainId appId)
{ {
if (appReservation != null) if (appReservation != null)
{ {
@ -140,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
await appsIndex.RebuildByContributorsAsync(context.AppId, contributors); await appsIndex.RebuildByContributorsAsync(context.AppId, contributors);
} }
private Task WriteAssetAsync(Guid appId, IBackupWriter writer) private Task WriteAssetAsync(DomainId appId, IBackupWriter writer)
{ {
return writer.WriteBlobAsync(AvatarFile, async stream => return writer.WriteBlobAsync(AvatarFile, async stream =>
{ {
@ -154,7 +153,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
}); });
} }
private async Task ReadAssetAsync(Guid appId, IBackupReader reader) private async Task ReadAssetAsync(DomainId appId, IBackupReader reader)
{ {
try try
{ {

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddLanguage : AppCommand public sealed class AddLanguage : AppUpdateCommand
{ {
public Language Language { get; set; } public Language Language { get; set; }
} }

8
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddPattern.cs

@ -5,13 +5,13 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddPattern : AppCommand public sealed class AddPattern : AppUpdateCommand
{ {
public Guid PatternId { get; set; } public DomainId PatternId { get; set; }
public string Name { get; set; } public string Name { get; set; }
@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public AddPattern() public AddPattern()
{ {
PatternId = Guid.NewGuid(); PatternId = DomainId.NewGuid();
} }
} }
} }

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

Loading…
Cancel
Save