diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 411f4a8e8..2dedb3a7a 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -13,13 +13,12 @@ using Squidex.Domain.Apps.Entities.Apps.State; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Apps { - public sealed class AppDomainObject : DomainObjectBase + public sealed class AppDomainObject : SquidexDomainObjectBase { private readonly InitialPatterns initialPatterns; diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs index 103cd9981..360f9134c 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs @@ -9,13 +9,12 @@ using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetDomainObject : DomainObjectBase + public sealed class AssetDomainObject : SquidexDomainObjectBase { public AssetDomainObject Create(CreateAsset command) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 0b46b12d7..d9eb1fa1d 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -10,13 +10,12 @@ using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Contents { - public sealed class ContentDomainObject : DomainObjectBase + public sealed class ContentDomainObject : SquidexDomainObjectBase { public ContentDomainObject Create(CreateContent command) { diff --git a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs index bc05ac8bd..706b3e352 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs @@ -9,13 +9,12 @@ using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Rules { - public sealed class RuleDomainObject : DomainObjectBase + public sealed class RuleDomainObject : SquidexDomainObjectBase { public void Create(CreateRule command) { diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs index cc081ee28..d4c862953 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs @@ -12,13 +12,12 @@ using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; namespace Squidex.Domain.Apps.Entities.Schemas { - public sealed class SchemaDomainObject : DomainObjectBase + public sealed class SchemaDomainObject : SquidexDomainObjectBase { private readonly FieldRegistry registry; diff --git a/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectBase.cs b/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectBase.cs new file mode 100644 index 000000000..63f0f94ed --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/SquidexDomainObjectBase.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities +{ + public abstract class SquidexDomainObjectBase : DomainObjectBase where T : IDomainState, new() + { + public override void RaiseEvent(Envelope @event) + { + if (@event.Payload is AppEvent appEvent) + { + @event.SetAppId(appEvent.AppId.Id); + } + + base.RaiseEvent(@event); + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs b/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs new file mode 100644 index 000000000..0673d3b5b --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs @@ -0,0 +1,28 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Globalization; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Events +{ + public static class SquidexHeaderExtensions + { + public static Guid AppId(this EnvelopeHeaders headers) + { + return headers[SquidexHeaders.AppId].ToGuid(CultureInfo.InvariantCulture); + } + + public static Envelope SetAppId(this Envelope envelope, Guid value) where T : class + { + envelope.Headers.Set(SquidexHeaders.AppId, value); + + return envelope; + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/SquidexHeaders.cs b/src/Squidex.Domain.Apps.Events/SquidexHeaders.cs new file mode 100644 index 000000000..e2f610a10 --- /dev/null +++ b/src/Squidex.Domain.Apps.Events/SquidexHeaders.cs @@ -0,0 +1,14 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Events +{ + public static class SquidexHeaders + { + public static readonly string AppId = "AppId"; + } +} diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs index 203dd0050..c4c41ef6d 100644 --- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs +++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/ProjectionClient.cs @@ -51,8 +51,8 @@ namespace Squidex.Infrastructure.EventSourcing $@"fromAll() .when({{ $any: function (s, e) {{ - if (e.streamId.indexOf('{prefix}') === 0 && e.data.{property}) {{ - linkTo('{name}-' + e.data.{property}, e); + if (e.streamId.indexOf('{prefix}') === 0 && e.metadata.{property}) {{ + linkTo('{name}-' + e.metadata.{property}, e); }} }} }});"; diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs index dfff1ef94..62d15ca20 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs @@ -7,7 +7,6 @@ using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json.Linq; -using Squidex.Infrastructure.Reflection; namespace Squidex.Infrastructure.EventSourcing { @@ -15,7 +14,7 @@ namespace Squidex.Infrastructure.EventSourcing { [BsonElement] [BsonRequired] - public JToken Payload { get; set; } + public string Payload { get; set; } [BsonElement] [BsonRequired] @@ -25,18 +24,14 @@ namespace Squidex.Infrastructure.EventSourcing [BsonRequired] public string Type { get; set; } - public MongoEvent() + public static MongoEvent FromEventData(EventData data) { - } - - public MongoEvent(EventData data) - { - SimpleMapper.Map(data, this); + return new MongoEvent { Type = data.Type, Metadata = data.Metadata, Payload = data.ToString() }; } public EventData ToEventData() { - return SimpleMapper.Map(this, new EventData()); + return new EventData { Type = Type, Metadata = Metadata, Payload = JObject.Parse(Payload) }; } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs index ccc7737e9..eed2d0bce 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs @@ -167,7 +167,7 @@ namespace Squidex.Infrastructure.EventSourcing private static string CreateIndexPath(string property) { - return $"Events.Payload.{property}"; + return $"Events.Metadata.{property}"; } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs index 016d3d837..937b66050 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs @@ -108,7 +108,7 @@ namespace Squidex.Infrastructure.EventSourcing foreach (var e in events) { - var mongoEvent = new MongoEvent(e); + var mongoEvent = MongoEvent.FromEventData(e); commitEvents[i++] = mongoEvent; } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs index 2a8d6e572..2d2dc8939 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs @@ -12,6 +12,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.MongoDb { diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs b/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs index 7327a058d..b054d8729 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs @@ -45,13 +45,13 @@ namespace Squidex.Infrastructure.Commands RaiseEvent(Envelope.Create(@event)); } - public void RaiseEvent(Envelope @event) where TEvent : class, IEvent + public virtual void RaiseEvent(Envelope @event) { Guard.NotNull(@event, nameof(@event)); @event.SetAggregateId(id); - ApplyEvent(@event.To()); + ApplyEvent(@event); snapshot.Version++; diff --git a/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs b/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs index 4499419d3..c4c83790f 100644 --- a/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs @@ -25,19 +25,14 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - var parts = reader.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) + try { - throw new JsonException("Named id must have more than 2 parts divided by commata."); + return NamedId.Parse(reader.Value.ToString(), Guid.TryParse); } - - if (!Guid.TryParse(parts[0], out var id)) + catch (ArgumentException ex) { - throw new JsonException("Named id must be a valid guid."); + throw new JsonException(ex.Message); } - - return new NamedId(id, string.Join(",", parts.Skip(1))); } } } diff --git a/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs b/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs index 4efb97dc0..5054b4dc7 100644 --- a/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs @@ -25,19 +25,14 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - var parts = reader.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) + try { - throw new JsonException("Named id must have more than 2 parts divided by commata."); + return NamedId.Parse(reader.Value.ToString(), long.TryParse); } - - if (!long.TryParse(parts[0], out var id)) + catch (ArgumentException ex) { - throw new JsonException("Named id must be a valid long."); + throw new JsonException(ex.Message); } - - return new NamedId(id, string.Join(",", parts.Skip(1))); } } } diff --git a/src/Squidex.Infrastructure/Migrations/Migrator.cs b/src/Squidex.Infrastructure/Migrations/Migrator.cs index e0f8feffe..0ba832d9e 100644 --- a/src/Squidex.Infrastructure/Migrations/Migrator.cs +++ b/src/Squidex.Infrastructure/Migrations/Migrator.cs @@ -17,7 +17,7 @@ namespace Squidex.Infrastructure.Migrations private readonly IMigrationStatus migrationStatus; private readonly IMigrationPath migrationPath; - public int LockWaitMs { get; set; } = 5000; + public int LockWaitMs { get; set; } = 500; public Migrator(IMigrationStatus migrationStatus, IMigrationPath migrationPath, ISemanticLog log) { diff --git a/src/Squidex.Infrastructure/NamedId.cs b/src/Squidex.Infrastructure/NamedId.cs index 0b16475fe..e8f99f6d4 100644 --- a/src/Squidex.Infrastructure/NamedId.cs +++ b/src/Squidex.Infrastructure/NamedId.cs @@ -6,9 +6,12 @@ // ========================================================================== using System; +using System.Linq; namespace Squidex.Infrastructure { + public delegate bool Parser(string input, out T result); + public sealed class NamedId : IEquatable> { public T Id { get; } @@ -44,5 +47,24 @@ namespace Squidex.Infrastructure { return (Id.GetHashCode() * 397) ^ Name.GetHashCode(); } + + public static NamedId Parse(string value, Parser parser) + { + Guard.NotNull(value, nameof(value)); + + var parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length < 2) + { + throw new ArgumentException("Named id must have more than 2 parts divided by commata."); + } + + if (!parser(parts[0], out var id)) + { + throw new ArgumentException("Named id must be a valid guid."); + } + + return new NamedId(id, string.Join(",", parts.Skip(1))); + } } } diff --git a/tools/Migrate_01/MigrationPath.cs b/tools/Migrate_01/MigrationPath.cs index 682c3c704..365bdf896 100644 --- a/tools/Migrate_01/MigrationPath.cs +++ b/tools/Migrate_01/MigrationPath.cs @@ -13,16 +13,16 @@ using Squidex.Infrastructure.Migrations; namespace Migrate_01 { - public class MigrationMatrix + public sealed class MigrationPath : IMigrationPath { private readonly IServiceProvider serviceProvider; - public MigrationMatrix(IServiceProvider serviceProvider) + public MigrationPath(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } - public (int Version, IEnumerable Migrations) MigrationPath(int version) + public (int Version, IEnumerable Migrations) GetNext(int version) { switch (version) { diff --git a/tools/Migrate_01/Migrations/ConvertEventStore.cs b/tools/Migrate_01/Migrations/ConvertEventStore.cs index 83715a400..20c74e8ab 100644 --- a/tools/Migrate_01/Migrations/ConvertEventStore.cs +++ b/tools/Migrate_01/Migrations/ConvertEventStore.cs @@ -5,11 +5,16 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; +using Newtonsoft.Json.Linq; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.MongoDb; namespace Migrate_01.Migrations { @@ -34,10 +39,16 @@ namespace Migrate_01.Migrations { foreach (BsonDocument @event in commit["Events"].AsBsonArray) { - @event.Remove("EventId"); + var meta = JObject.Parse(@event["Metadata"].AsString); + var data = JObject.Parse(@event["Payload"].AsString); + + if (data.TryGetValue("appId", out var appId)) + { + meta[SquidexHeaders.AppId] = NamedId.Parse(appId.ToString(), Guid.TryParse).Id; + } - @event["Payload"] = BsonDocument.Parse(@event["Payload"].AsString); - @event["Metadata"] = BsonDocument.Parse(@event["Metadata"].AsString); + @event.Remove("EventId"); + @event["Metadata"] = meta.ToBson(); } await collection.ReplaceOneAsync(filter.Eq("_id", commit["_id"].AsString), commit);