diff --git a/backend/Squidex.sln b/backend/Squidex.sln index 7f3458932a..2d9c700507 100644 --- a/backend/Squidex.sln +++ b/backend/Squidex.sln @@ -16,16 +16,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Core.Tests", "tests\Squidex.Domain.Apps.Core.Tests\Squidex.Domain.Apps.Core.Tests.csproj", "{FD0AFD44-7A93-4F9E-B5ED-72582392E435}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj", "{6A811927-3C37-430A-90F4-503E37123956}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{94207AA6-4923-4183-A558-E0F8196B8CA3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Shared", "src\Squidex.Shared\Squidex.Shared.csproj", "{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users", "src\Squidex.Domain.Users\Squidex.Domain.Users.csproj", "{F7771E22-47BD-45C4-A133-FD7F1DE27CA0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users.MongoDb", "src\Squidex.Domain.Users.MongoDb\Squidex.Domain.Users.MongoDb.csproj", "{27CF800D-890F-4882-BF05-44EC3233537D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Users.Tests", "tests\Squidex.Domain.Users.Tests\Squidex.Domain.Users.Tests.csproj", "{42184546-E3CB-4D4F-9495-43979B9C63B9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{360C300D-0F7E-439D-A437-714C959E3CAD}" @@ -42,8 +38,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entitie EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entities.Tests", "tests\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj", "{AA003372-CD8D-4DBC-962C-F61E0C93CF05}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entities.MongoDb", "src\Squidex.Domain.Apps.Entities.MongoDb\Squidex.Domain.Apps.Entities.MongoDb.csproj", "{7DA5B308-D950-4496-93D5-21D6C4D91644}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Web.Tests", "tests\Squidex.Web.Tests\Squidex.Web.Tests.csproj", "{7E8CC864-4C6E-496F-A672-9F9AD8874835}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{FB8BC3A2-2010-4C3C-A87D-D4A98C05EE52}" @@ -56,7 +50,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Web", "src\Squidex. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migrations", "src\Migrations\Migrations.csproj", "{23615A39-F3FB-4575-A91C-535899DFB636}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.GetEventStore", "src\Squidex.Infrastructure.GetEventStore\Squidex.Infrastructure.GetEventStore.csproj", "{4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{3378B841-53F8-48CC-87C1-1B30CF912BFD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Data.MongoDb", "src\Squidex.Data.MongoDb\Squidex.Data.MongoDb.csproj", "{F754F05E-02FF-47B2-AB46-BB05C7E6B29D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Data.Tests", "tests\Squidex.Data.Tests\Squidex.Data.Tests.csproj", "{AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -108,14 +106,6 @@ Global {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Release|Any CPU.Build.0 = Release|Any CPU {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Release|x64.ActiveCfg = Release|Any CPU {FD0AFD44-7A93-4F9E-B5ED-72582392E435}.Release|x86.ActiveCfg = Release|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Debug|x64.ActiveCfg = Debug|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Debug|x86.ActiveCfg = Debug|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Release|Any CPU.Build.0 = Release|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Release|x64.ActiveCfg = Release|Any CPU - {6A811927-3C37-430A-90F4-503E37123956}.Release|x86.ActiveCfg = Release|Any CPU {5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -140,18 +130,6 @@ Global {F7771E22-47BD-45C4-A133-FD7F1DE27CA0}.Release|x64.Build.0 = Release|Any CPU {F7771E22-47BD-45C4-A133-FD7F1DE27CA0}.Release|x86.ActiveCfg = Release|Any CPU {F7771E22-47BD-45C4-A133-FD7F1DE27CA0}.Release|x86.Build.0 = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|x64.ActiveCfg = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|x64.Build.0 = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|x86.ActiveCfg = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Debug|x86.Build.0 = Debug|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|Any CPU.Build.0 = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|x64.ActiveCfg = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|x64.Build.0 = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|x86.ActiveCfg = Release|Any CPU - {27CF800D-890F-4882-BF05-44EC3233537D}.Release|x86.Build.0 = Release|Any CPU {42184546-E3CB-4D4F-9495-43979B9C63B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42184546-E3CB-4D4F-9495-43979B9C63B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {42184546-E3CB-4D4F-9495-43979B9C63B9}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -212,18 +190,6 @@ Global {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x64.Build.0 = Release|Any CPU {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x86.ActiveCfg = Release|Any CPU {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x86.Build.0 = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x64.ActiveCfg = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x64.Build.0 = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x86.ActiveCfg = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Debug|x86.Build.0 = Debug|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|Any CPU.Build.0 = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x64.ActiveCfg = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x64.Build.0 = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x86.ActiveCfg = Release|Any CPU - {7DA5B308-D950-4496-93D5-21D6C4D91644}.Release|x86.Build.0 = Release|Any CPU {7E8CC864-4C6E-496F-A672-9F9AD8874835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E8CC864-4C6E-496F-A672-9F9AD8874835}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E8CC864-4C6E-496F-A672-9F9AD8874835}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -272,18 +238,30 @@ Global {23615A39-F3FB-4575-A91C-535899DFB636}.Release|x64.Build.0 = Release|Any CPU {23615A39-F3FB-4575-A91C-535899DFB636}.Release|x86.ActiveCfg = Release|Any CPU {23615A39-F3FB-4575-A91C-535899DFB636}.Release|x86.Build.0 = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|x64.ActiveCfg = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|x64.Build.0 = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|x86.ActiveCfg = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Debug|x86.Build.0 = Debug|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|Any CPU.Build.0 = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|x64.ActiveCfg = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|x64.Build.0 = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|x86.ActiveCfg = Release|Any CPU - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC}.Release|x86.Build.0 = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|x64.ActiveCfg = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|x64.Build.0 = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|x86.ActiveCfg = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Debug|x86.Build.0 = Debug|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|Any CPU.Build.0 = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|x64.ActiveCfg = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|x64.Build.0 = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|x86.ActiveCfg = Release|Any CPU + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D}.Release|x86.Build.0 = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|x64.Build.0 = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Debug|x86.Build.0 = Debug|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|Any CPU.Build.0 = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|x64.ActiveCfg = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|x64.Build.0 = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|x86.ActiveCfg = Release|Any CPU + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -293,21 +271,19 @@ Global {25F66C64-058A-4D44-BC0C-F12A054F9A91} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} - {6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {5E75AB7D-6F01-4313-AFF1-7F7128FFD71F} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} {F7771E22-47BD-45C4-A133-FD7F1DE27CA0} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} - {27CF800D-890F-4882-BF05-44EC3233537D} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} {42184546-E3CB-4D4F-9495-43979B9C63B9} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} {F0A83301-50A5-40EA-A1A2-07C7858F5A3F} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {6B3F75B6-5888-468E-BA4F-4FC725DAEF31} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {79FEF326-CA5E-4698-B2BA-C16A4580B4D5} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {AA003372-CD8D-4DBC-962C-F61E0C93CF05} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} - {7DA5B308-D950-4496-93D5-21D6C4D91644} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {7E8CC864-4C6E-496F-A672-9F9AD8874835} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} {F3C41B82-6A67-409A-B7FE-54543EE4F38B} = {FB8BC3A2-2010-4C3C-A87D-D4A98C05EE52} {5B2D251F-46E3-486A-AE16-E3FE06B559ED} = {7EDE8CF1-B1E4-4005-B154-834B944E0D7A} {23615A39-F3FB-4575-A91C-535899DFB636} = {94207AA6-4923-4183-A558-E0F8196B8CA3} - {4CFBD9FF-6565-457E-B81C-9FCEFEE854BC} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} + {F754F05E-02FF-47B2-AB46-BB05C7E6B29D} = {3378B841-53F8-48CC-87C1-1B30CF912BFD} + {AA2F3C32-E3C8-4DF3-A365-F25C7EC19BCD} = {3378B841-53F8-48CC-87C1-1B30CF912BFD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08} diff --git a/backend/src/Migrations/Migrations.csproj b/backend/src/Migrations/Migrations.csproj index 06781ee030..2908dae94e 100644 --- a/backend/src/Migrations/Migrations.csproj +++ b/backend/src/Migrations/Migrations.csproj @@ -14,11 +14,10 @@ + - - diff --git a/backend/src/Migrations/Migrations/ConvertEventStore.cs b/backend/src/Migrations/Migrations/ConvertEventStore.cs index bfe2dc64d7..bd75add736 100644 --- a/backend/src/Migrations/Migrations/ConvertEventStore.cs +++ b/backend/src/Migrations/Migrations/ConvertEventStore.cs @@ -7,9 +7,10 @@ using MongoDB.Bson; using MongoDB.Driver; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Events; +using Squidex.Events.Mongo; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations; diff --git a/backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs b/backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs index 842c0fcfef..0ed68b0c5a 100644 --- a/backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs +++ b/backend/src/Migrations/Migrations/ConvertEventStoreAppId.cs @@ -7,10 +7,10 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Events; +using Squidex.Events.Mongo; using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations; diff --git a/backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs b/backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs index 5c272abae2..d8ed6c0452 100644 --- a/backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs +++ b/backend/src/Migrations/Migrations/MongoDb/AddAppIdToEventStream.cs @@ -10,7 +10,6 @@ using MongoDB.Driver; using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Tasks; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs b/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs index ca28142d8a..e0f040b866 100644 --- a/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs +++ b/backend/src/Migrations/Migrations/MongoDb/ConvertDocumentIds.cs @@ -9,7 +9,6 @@ using MongoDB.Driver; using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Tasks; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs b/backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs index 8e473107f6..3e66fea701 100644 --- a/backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs +++ b/backend/src/Migrations/Migrations/MongoDb/ConvertOldSnapshotStores.cs @@ -7,8 +7,8 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs b/backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs index d8041c49eb..2733e53bf9 100644 --- a/backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs +++ b/backend/src/Migrations/Migrations/MongoDb/ConvertRuleEventsJson.cs @@ -7,8 +7,8 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs b/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs index 4b3d775389..09c4a7969f 100644 --- a/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs +++ b/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs @@ -11,7 +11,6 @@ using Squidex.Domain.Apps.Entities.Rules; using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs b/backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs index 2356a48137..ebdcc0a28c 100644 --- a/backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs +++ b/backend/src/Migrations/Migrations/MongoDb/RenameAssetMetadata.cs @@ -7,8 +7,8 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs b/backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs index 6249f205e0..0a2e80efe7 100644 --- a/backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs +++ b/backend/src/Migrations/Migrations/MongoDb/RenameAssetSlugField.cs @@ -7,8 +7,8 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs b/backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs index cd62d015c2..30c06df6a0 100644 --- a/backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs +++ b/backend/src/Migrations/Migrations/MongoDb/RestructureContentCollection.cs @@ -7,8 +7,8 @@ using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrations.Migrations.MongoDb; diff --git a/backend/src/Migrations/RebuilderExtensions.cs b/backend/src/Migrations/RebuilderExtensions.cs index 334949a5b7..580c20123a 100644 --- a/backend/src/Migrations/RebuilderExtensions.cs +++ b/backend/src/Migrations/RebuilderExtensions.cs @@ -15,8 +15,8 @@ using Squidex.Domain.Apps.Entities.Contents.DomainObject; using Squidex.Domain.Apps.Entities.Rules.DomainObject; using Squidex.Domain.Apps.Entities.Schemas.DomainObject; +using Squidex.Events; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.EventSourcing; namespace Migrations; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/AdaptIdVisitor.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/AdaptIdVisitor.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/AdaptIdVisitor.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/AdaptIdVisitor.cs index fcbcb0eec5..cf2955d5c5 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/AdaptIdVisitor.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/AdaptIdVisitor.cs @@ -11,7 +11,7 @@ #pragma warning disable SA1313 // Parameter names should begin with lower-case letter -namespace Squidex.Domain.Apps.Entities.MongoDb; +namespace Squidex.Domain.Apps.Entities; internal sealed class AdaptIdVisitor : TransformVisitor { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppEntity.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppEntity.cs index 1dd6a81f04..d46ce61058 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppEntity.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Apps; +namespace Squidex.Domain.Apps.Entities.Apps; public sealed class MongoAppEntity : MongoState { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppRepository.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppRepository.cs index caf03b7c77..cf0fd46c76 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Apps/MongoAppRepository.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Apps; +namespace Squidex.Domain.Apps.Entities.Apps; public sealed class MongoAppRepository(IMongoDatabase database) : MongoSnapshotStoreBase(database), IAppRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/AssetItemClassMap.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/AssetItemClassMap.cs index 49c41a718d..54f1fec2c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/AssetItemClassMap.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/AssetItemClassMap.cs @@ -8,7 +8,7 @@ using MongoDB.Bson.Serialization; using Squidex.Domain.Apps.Core.Assets; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; +namespace Squidex.Domain.Apps.Entities.Assets; internal static class AssetItemClassMap { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetEntity.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetEntity.cs index 6c20a54c6a..a9cbdccb5c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetEntity.cs @@ -8,11 +8,10 @@ using MongoDB.Bson.Serialization; using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; +namespace Squidex.Domain.Apps.Entities.Assets; public record MongoAssetEntity : Asset, IVersionedEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderEntity.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderEntity.cs index 19c65a6efb..6112dfe8c9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderEntity.cs @@ -8,11 +8,10 @@ using MongoDB.Bson.Serialization; using Squidex.Domain.Apps.Core.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; +namespace Squidex.Domain.Apps.Entities.Assets; public record MongoAssetFolderEntity : AssetFolder, IVersionedEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository.cs index b4045fa7f0..608cad584c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository.cs @@ -7,9 +7,9 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository_SnapshotStore.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository_SnapshotStore.cs index 6b128d923a..e20d62bf52 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository_SnapshotStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetFolderRepository_SnapshotStore.cs @@ -8,8 +8,8 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; #pragma warning disable MA0048 // File name must match type name diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository.cs index d84d3231b5..b38c64d7ec 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository.cs @@ -9,11 +9,12 @@ using Microsoft.Extensions.Logging; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; -using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; +using Squidex.Domain.Apps.Entities.Assets.Visitors; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.MongoDb.Queries; +using Squidex.Infrastructure.Counts; +using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; @@ -99,7 +100,7 @@ public async Task> QueryAsync(DomainId appId, DomainId? paren try { // We need to translate the query names to the document field names in MongoDB. - var query = q.Query.AdjustToModel(appId); + var query = q.Query.AdjustToAssetModel(appId); if (q.Ids is { Count: > 0 }) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository_SnapshotStore.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository_SnapshotStore.cs index 337689b476..6e86ca64cc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository_SnapshotStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoAssetRepository_SnapshotStore.cs @@ -8,8 +8,8 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; #pragma warning disable MA0048 // File name must match type name diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoShardedAssetRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoShardedAssetRepository.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoShardedAssetRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoShardedAssetRepository.cs index b900b0680e..832580e36b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoShardedAssetRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/MongoShardedAssetRepository.cs @@ -8,10 +8,11 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.MongoDb.Assets; using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets; +namespace Squidex.Domain.Apps.Entities.Assets; public sealed class MongoShardedAssetRepository(IShardingStrategy sharding, Func factory) : ShardedSnapshotStore(sharding, factory, x => x.AppId.Id), IAssetRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FindExtensions.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FindExtensions.cs index 4da26991a9..043ad189b9 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FindExtensions.cs @@ -7,16 +7,15 @@ using MongoDB.Driver; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; +namespace Squidex.Domain.Apps.Entities.Assets.Visitors; public static class FindExtensions { private static readonly FilterDefinitionBuilder Filter = Builders.Filter; - public static ClrQuery AdjustToModel(this ClrQuery query, DomainId appId) + public static ClrQuery AdjustToAssetModel(this ClrQuery query, DomainId appId) { if (query.Filter != null) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathConverter.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathConverter.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathConverter.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathConverter.cs index 99b9ca2f67..f4185e05bf 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathConverter.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathConverter.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; +namespace Squidex.Domain.Apps.Entities.Assets.Visitors; public sealed class FirstPascalPathConverter : TransformVisitor { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathExtension.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathExtension.cs similarity index 91% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathExtension.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathExtension.cs index 96ff823bb6..b48ce840b8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FirstPascalPathExtension.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Assets/Visitors/FirstPascalPathExtension.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure.Queries; using Squidex.Text; -namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; +namespace Squidex.Domain.Apps.Entities.Assets.Visitors; public static class FirstPascalPathExtension { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/BsonUniqueContentIdSerializer.IdInfo.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/BsonUniqueContentIdSerializer.IdInfo.cs similarity index 100% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/BsonUniqueContentIdSerializer.IdInfo.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/BsonUniqueContentIdSerializer.IdInfo.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/BsonUniqueContentIdSerializer.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/BsonUniqueContentIdSerializer.cs similarity index 100% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/BsonUniqueContentIdSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/BsonUniqueContentIdSerializer.cs diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/IndexParser.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/IndexParser.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/IndexParser.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/IndexParser.cs index fc7677dcdc..f42e215e02 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/IndexParser.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/IndexParser.cs @@ -7,11 +7,11 @@ using System.Diagnostics.CodeAnalysis; using MongoDB.Bson; -using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +using Squidex.Domain.Apps.Entities.Contents.Operations; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents; +namespace Squidex.Domain.Apps.Entities.Contents; public static class IndexParser { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentCollection.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentCollection.cs index 4ea3e5dbe6..371055be8d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentCollection.cs @@ -11,16 +11,16 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +using Squidex.Domain.Apps.Entities.Contents.Operations; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.Counts; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Translations; #pragma warning disable IDE0060 // Remove unused parameter -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents; +namespace Squidex.Domain.Apps.Entities.Contents; public sealed class MongoContentCollection : MongoRepositoryBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentEntity.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentEntity.cs index dd3142a786..881031c953 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentEntity.cs @@ -10,10 +10,9 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents; +namespace Squidex.Domain.Apps.Entities.Contents; public record MongoContentEntity : Content, IVersionedEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository.cs similarity index 99% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository.cs index eec20accf2..d8588e8be8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository.cs @@ -18,7 +18,6 @@ using Squidex.Hosting; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository_SnapshotStore.cs similarity index 99% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository_SnapshotStore.cs index 48a4d7d7ee..0a396c0433 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoContentRepository_SnapshotStore.cs @@ -8,6 +8,7 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoShardedContentRepository.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoShardedContentRepository.cs index 783aaecf6f..db651d74a7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoShardedContentRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/MongoShardedContentRepository.cs @@ -10,13 +10,13 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents; +namespace Squidex.Domain.Apps.Entities.Contents; public sealed class MongoShardedContentRepository(IShardingStrategy sharding, Func factory) : ShardedSnapshotStore(sharding, factory, x => x.AppId.Id), IContentRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Adapt.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Adapt.cs index ff4b2984e2..2a3f218354 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Adapt.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Adapt.cs @@ -8,11 +8,10 @@ using GraphQL; using MongoDB.Bson.Serialization; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.OData; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; public static class Adapt { @@ -105,7 +104,7 @@ public static PropertyPath MapPathReverse(PropertyPath path) return result; } - public static ClrQuery AdjustToModel(this ClrQuery query, DomainId appId) + public static ClrQuery AdjustToContentModel(this ClrQuery query, DomainId appId) { if (query.Filter != null) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/AdaptionVisitor.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/AdaptionVisitor.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/AdaptionVisitor.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/AdaptionVisitor.cs index a889e5cd57..fb1bc19832 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/AdaptionVisitor.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/AdaptionVisitor.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class AdaptionVisitor : TransformVisitor { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Extensions.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Extensions.cs index 94eb3390ec..d413d977dd 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/Extensions.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/Extensions.cs @@ -12,11 +12,9 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; public static class Extensions { @@ -182,12 +180,12 @@ public static Task> FindStatusAsync(this IMongoCollection SelectFields(this IFindFluent find, IEnumerable? fields) { - return find.Project(BuildProjection(fields)); + return find.Project(BuildProjection(fields)); } public static IAggregateFluent SelectFields(this IAggregateFluent find, IEnumerable? fields) { - return find.Project(BuildProjection(fields)); + return find.Project(BuildProjection(fields)); } public static ProjectionDefinition BuildProjection(IEnumerable? fields) diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/OperationBase.cs similarity index 88% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/OperationBase.cs index 58d60ff2e0..2288073126 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/OperationBase.cs @@ -6,9 +6,9 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; public abstract class OperationBase : MongoBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryAsStream.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryAsStream.cs index 8af2f385ba..1a9247963d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryAsStream.cs @@ -11,7 +11,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; public sealed class QueryAsStream : OperationBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryById.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryById.cs index d2e7e576a6..4e02ad8d7b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryById.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryById.cs @@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryById : OperationBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByIds.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByIds.cs index c315a30b47..c30478d630 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByIds.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByIds.cs @@ -11,11 +11,9 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryByIds : OperationBase { @@ -44,7 +42,7 @@ public async Task> QueryAsync(App app, List schemas } // We need to translate the query names to the document field names in MongoDB. - var query = q.Query.AdjustToModel(app.Id); + var query = q.Query.AdjustToContentModel(app.Id); // Create a filter from the Ids and ensure that the content ids match to the schema IDs. var filter = CreateFilter(app.Id, schemas.Select(x => x.Id), q.Ids.ToHashSet(), query.Filter); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByQuery.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByQuery.cs index 202627add5..70616998c6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryByQuery.cs @@ -10,11 +10,10 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.MongoDb.Queries; +using Squidex.Infrastructure.Counts; using Squidex.Infrastructure.Queries; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryByQuery(MongoCountCollection countCollection) : OperationBase { @@ -52,7 +51,7 @@ public async Task> QueryAsync(App app, List schemas CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. - var query = q.Query.AdjustToModel(app.Id); + var query = q.Query.AdjustToContentModel(app.Id); var (filter, isDefault) = CreateFilter(app.Id, schemas.Select(x => x.Id), query, q.Reference, q.CreatedBy); @@ -83,7 +82,7 @@ public async Task> QueryAsync(Schema schema, Q q, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. - var query = q.Query.AdjustToModel(schema.AppId.Id); + var query = q.Query.AdjustToContentModel(schema.AppId.Id); // Default means that no other filters are applied and we only query by app and schema. var (filter, isDefault) = CreateFilter(schema.AppId.Id, Enumerable.Repeat(schema.Id, 1), query, q.Reference, q.CreatedBy); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryInDedicatedCollection.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryInDedicatedCollection.cs index 87ffb7ba75..88c7ebf19a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryInDedicatedCollection.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryInDedicatedCollection.cs @@ -11,12 +11,10 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryInDedicatedCollection(IMongoClient mongoClient, string prefixDatabase, string prefixCollection) : MongoBase { @@ -70,7 +68,7 @@ public async Task> QueryAsync(Schema schema, Q q, CancellationToken ct) { // We need to translate the query names to the document field names in MongoDB. - var query = q.Query.AdjustToModel(schema.AppId.Id); + var query = q.Query.AdjustToContentModel(schema.AppId.Id); var filter = CreateFilter(query, q.Reference, q.CreatedBy); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferences.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferences.cs index 4d9f60e531..95a91b1ab8 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferences.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferences.cs @@ -12,7 +12,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryReferences(QueryByIds queryByIds) : OperationBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferrers.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferrers.cs index e2eb91279d..39cdd3bc55 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryReferrers.cs @@ -10,9 +10,8 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryReferrers : OperationBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryScheduled.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryScheduled.cs index 494e338591..22cf46a7c6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Contents/Operations/QueryScheduled.cs @@ -11,11 +11,10 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; #pragma warning disable MA0073 // Avoid comparison with bool constant -namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +namespace Squidex.Domain.Apps.Entities.Contents.Operations; internal sealed class QueryScheduled : OperationBase { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/EntityClassMap.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/EntityClassMap.cs index f0abbe9274..89646ee6a7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/EntityClassMap.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/EntityClassMap.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Entities.MongoDb; +namespace Squidex.Domain.Apps.Entities; internal static class EntityClassMap { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/History/MongoHistoryEventRepository.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/History/MongoHistoryEventRepository.cs index 3ee597545e..1337a19041 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/History/MongoHistoryEventRepository.cs @@ -8,12 +8,10 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.History; +namespace Squidex.Domain.Apps.Entities.History; public sealed class MongoHistoryEventRepository(IMongoDatabase database) : MongoRepositoryBase(database), IHistoryEventRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEntity.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEntity.cs index ebd684e3a2..2a2770f737 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEntity.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; +namespace Squidex.Domain.Apps.Entities.Rules; public sealed class MongoRuleEntity : MongoState { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventEntity.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventEntity.cs index 5fcff46fcc..6f060354ed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventEntity.cs @@ -10,13 +10,11 @@ using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; +namespace Squidex.Domain.Apps.Entities.Rules; public sealed class MongoRuleEventEntity : IRuleEventEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventRepository.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventRepository.cs index 7140d8b4ef..73a7e73ce4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleEventRepository.cs @@ -9,12 +9,10 @@ using NodaTime; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; +namespace Squidex.Domain.Apps.Entities.Rules; public sealed class MongoRuleEventRepository(IMongoDatabase database) : MongoRepositoryBase(database), IRuleEventRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleRepository.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleRepository.cs index 3d01cc4df7..ab232a3beb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Rules/MongoRuleRepository.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Rules; +namespace Squidex.Domain.Apps.Entities.Rules; public sealed class MongoRuleRepository(IMongoDatabase database) : MongoSnapshotStoreBase(database), IRuleRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaEntity.cs similarity index 95% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaEntity.cs index 51aaaf4482..3471d980de 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaEntity.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; +namespace Squidex.Domain.Apps.Entities.Schemas; public sealed class MongoSchemaEntity : MongoState { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaRepository.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaRepository.cs index e842caa59a..f6249bc922 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemaRepository.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; +namespace Squidex.Domain.Apps.Entities.Schemas; public sealed class MongoSchemaRepository(IMongoDatabase database) : MongoSnapshotStoreBase(database), ISchemaRepository, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHash.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHash.cs index b6a052c947..9f33e89119 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHash.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHash.cs @@ -9,14 +9,13 @@ using NodaTime; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Events; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.ObjectPool; -namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; +namespace Squidex.Domain.Apps.Entities.Schemas; public sealed class MongoSchemasHash(IMongoDatabase database) : MongoRepositoryBase(database), ISchemasHash, IEventConsumer, IDeleter { @@ -55,7 +54,7 @@ public Task On(IEnumerable> events) Filter.Eq(x => x.AppId, schemaEvent.AppId.Id), Update .Set($"s.{schemaEvent.SchemaId.Id}", @event.Headers.EventStreamNumber()) - .Set(x => x.Updated, @event.Headers.Timestamp())) + .Set(x => x.Updated, @event.Headers.TimestampAsInstant())) { IsUpsert = true }); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHashEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHashEntity.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHashEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHashEntity.cs index 4a48735e8c..9de3410a7e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemasHashEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Schemas/MongoSchemasHashEntity.cs @@ -9,7 +9,7 @@ using NodaTime; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas; +namespace Squidex.Domain.Apps.Entities.Schemas; public sealed class MongoSchemasHashEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/ShardedSnapshotStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/ShardedSnapshotStore.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/ShardedSnapshotStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/ShardedSnapshotStore.cs index a08859f930..d7d6f89888 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/ShardedSnapshotStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/ShardedSnapshotStore.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb; +namespace Squidex.Domain.Apps.Entities; public abstract class ShardedSnapshotStore(IShardingStrategy sharding, Func factory, Func getShardKey) : ShardedService(sharding, factory), ISnapshotStore, IDeleter where TStore : ISnapshotStore, IDeleter { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamEntity.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamEntity.cs index 92b5c875a1..fbfe66fe41 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamEntity.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Teams; +namespace Squidex.Domain.Apps.Entities.Teams; public sealed class MongoTeamEntity : MongoState { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamRepository.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamRepository.cs index bd20877f10..179222d192 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Teams/MongoTeamRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Teams/MongoTeamRepository.cs @@ -11,7 +11,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Teams; +namespace Squidex.Domain.Apps.Entities.Teams; public sealed class MongoTeamRepository(IMongoDatabase database) : MongoSnapshotStoreBase(database), ITeamRepository { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasIndexDefinition.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasIndexDefinition.cs similarity index 99% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasIndexDefinition.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasIndexDefinition.cs index eea6ad2746..e24a2dfa7e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasIndexDefinition.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasIndexDefinition.cs @@ -8,7 +8,7 @@ using System.Net.Http.Json; using Squidex.Hosting.Configuration; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public static class AtlasIndexDefinition { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasOptions.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasOptions.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasOptions.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasOptions.cs index 2e3b9ca216..ce963a7210 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasOptions.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasOptions.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class AtlasOptions { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasTextIndex.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasTextIndex.cs index 08a6c9af16..24beace929 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/AtlasTextIndex.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/AtlasTextIndex.cs @@ -17,7 +17,7 @@ using Squidex.Infrastructure; using LuceneQueryAnalyzer = Lucene.Net.QueryParsers.Classic.QueryParser; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class AtlasTextIndex(IMongoDatabase database, IHttpClientFactory atlasClient, IOptions atlasOptions, string shardKey) : MongoTextIndexBase>(database, shardKey, new CommandFactory>(BuildTexts)) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/CommandFactory.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/CommandFactory.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/CommandFactory.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/CommandFactory.cs index 9534023728..9bf94b89e3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/CommandFactory.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/CommandFactory.cs @@ -8,9 +8,9 @@ using MongoDB.Bson; using MongoDB.Driver; using Squidex.Domain.Apps.Entities.Contents.Text; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class CommandFactory(Func, T> textBuilder) : MongoBase> where T : class { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneQueryVisitor.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneQueryVisitor.cs similarity index 99% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneQueryVisitor.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneQueryVisitor.cs index dae951b178..d28c879f48 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneQueryVisitor.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneQueryVisitor.cs @@ -13,7 +13,7 @@ using MongoDB.Bson; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class LuceneQueryVisitor(Func? fieldConverter = null) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneSearchDefinitionExtensions.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneSearchDefinitionExtensions.cs similarity index 96% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneSearchDefinitionExtensions.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneSearchDefinitionExtensions.cs index c5dc8293a2..6888a1bdcf 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneSearchDefinitionExtensions.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/LuceneSearchDefinitionExtensions.cs @@ -9,7 +9,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public static class LuceneSearchDefinitionExtensions { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoShardedTextIndex.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoShardedTextIndex.cs index 330aae00ef..268a522788 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoShardedTextIndex.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoShardedTextIndex.cs @@ -12,7 +12,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class MongoShardedTextIndex(IShardingStrategy sharding, Func> factory) : ShardedService>(sharding, factory), ITextIndex, IDeleter where T : class { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndex.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndex.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndex.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndex.cs index 75044b7df1..d45889d241 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndex.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndex.cs @@ -11,7 +11,7 @@ using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class MongoTextIndex(IMongoDatabase database, string shardKey) : MongoTextIndexBase>(database, shardKey, new CommandFactory>(BuildTexts)) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexBase.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexBase.cs index 7ac90caca5..9c50d92c73 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexBase.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexBase.cs @@ -13,9 +13,8 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public abstract class MongoTextIndexBase(IMongoDatabase database, string shardKey, CommandFactory factory) : MongoRepositoryBase>(database), ITextIndex, IDeleter where T : class { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntity.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntity.cs similarity index 94% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntity.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntity.cs index 526694bfba..28cad20bc6 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntity.cs @@ -9,9 +9,8 @@ using MongoDB.Bson.Serialization.Attributes; using NetTopologySuite.Geometries; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class MongoTextIndexEntity { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntityText.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntityText.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntityText.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntityText.cs index e881f2fcd4..75261481ff 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexEntityText.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexEntityText.cs @@ -7,7 +7,7 @@ using MongoDB.Bson.Serialization.Attributes; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class MongoTextIndexEntityText { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexerState.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexerState.cs index bfc93ae81d..a1f1bc238b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/MongoTextIndexerState.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/MongoTextIndexerState.cs @@ -13,10 +13,10 @@ using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text.State; +using Squidex.Domain.Apps.Entities.MongoDb; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public sealed class MongoTextIndexerState( IMongoDatabase database, diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/Tokenizer.cs b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/Tokenizer.cs similarity index 98% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/Tokenizer.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/Tokenizer.cs index 3eaeb61e6f..8b3138bf5b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/Tokenizer.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Apps/Entities/Text/Tokenizer.cs @@ -16,7 +16,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.ObjectPool; -namespace Squidex.Domain.Apps.Entities.MongoDb.Text; +namespace Squidex.Domain.Apps.Entities.Text; public static class Tokenizer { diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoRoleStore.cs similarity index 99% rename from backend/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Users/MongoRoleStore.cs index b3aaa53331..512b25d0e7 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/MongoRoleStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoRoleStore.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure; namespace Squidex.Domain.Users.MongoDb; diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUser.cs similarity index 100% rename from backend/src/Squidex.Domain.Users.MongoDb/MongoUser.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUser.cs diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs similarity index 99% rename from backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs rename to backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs index 80b514da62..86d7d93080 100644 --- a/backend/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs +++ b/backend/src/Squidex.Data.MongoDb/Domain/Users/MongoUserStore.cs @@ -11,7 +11,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure; namespace Squidex.Domain.Users.MongoDb; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Batching.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Batching.cs similarity index 93% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Batching.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Batching.cs index 1be716fea5..186e699ad5 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Batching.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Batching.cs @@ -7,7 +7,7 @@ using MongoDB.Driver; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class Batching { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDefaultConventions.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDefaultConventions.cs similarity index 96% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDefaultConventions.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDefaultConventions.cs index 4ee7c52d26..f3a3bcfcf2 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDefaultConventions.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDefaultConventions.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.Serializers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class BsonDefaultConventions { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDomainIdSerializer.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDomainIdSerializer.cs index fc960b94e5..63892db39c 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonDomainIdSerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonDomainIdSerializer.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonDomainIdSerializer : SerializerBase, IBsonPolymorphicSerializer, IRepresentationConfigurable { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonEscapedDictionarySerializer.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonEscapedDictionarySerializer.cs index e234c4daf3..ef83ce1be2 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonEscapedDictionarySerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonEscapedDictionarySerializer.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonEscapedDictionarySerializer : ClassSerializerBase where TInstance : Dictionary, new() { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonHelper.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonHelper.cs index 07975565c0..ad85f8a830 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonHelper.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonHelper.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class BsonHelper { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonInstantSerializer.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonInstantSerializer.cs index 171aa0bac7..c3a443f1cf 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonInstantSerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonInstantSerializer.cs @@ -11,7 +11,7 @@ using NodaTime; using NodaTime.Text; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonInstantSerializer : SerializerBase, IBsonPolymorphicSerializer, IRepresentationConfigurable { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonAttribute.cs similarity index 91% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonAttribute.cs index 49c2bd8fe0..695cce0153 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonAttribute.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonAttribute.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; [AttributeUsage(AttributeTargets.Property)] public sealed class BsonJsonAttribute : Attribute diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonConvention.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonConvention.cs index 155f8845c9..df8ff65b17 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConvention.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonConvention.cs @@ -11,7 +11,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class BsonJsonConvention { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonSerializer.cs similarity index 99% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonSerializer.cs index f6e713500a..8176ecb411 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonSerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonSerializer.cs @@ -13,7 +13,7 @@ using MongoDB.Bson.Serialization.Serializers; using Squidex.Infrastructure.ObjectPool; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonJsonSerializer : SerializerBase, IRepresentationConfigurable> where T : class { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonValueSerializer.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonValueSerializer.cs index 8cecf16fce..bc8d8645a4 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonValueSerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonJsonValueSerializer.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization.Serializers; using Squidex.Infrastructure.Json.Objects; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonJsonValueSerializer : SerializerBase { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonStringSerializer.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/BsonStringSerializer.cs index 9648033fc4..65850089bf 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonStringSerializer.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/BsonStringSerializer.cs @@ -10,7 +10,7 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class BsonStringSerializer : SerializerBase { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Caching/MongoCacheEntry.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Caching/MongoCacheEntry.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/Caching/MongoCacheEntry.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Caching/MongoCacheEntry.cs diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Caching/MongoDistributedCache.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Caching/MongoDistributedCache.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/Caching/MongoDistributedCache.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Caching/MongoDistributedCache.cs index 4000675dae..1656286938 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/Caching/MongoDistributedCache.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Caching/MongoDistributedCache.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Caching.Distributed; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.Caching; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountCollection.cs similarity index 97% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountCollection.cs index 70d96f1fd7..ff0ab65cae 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountCollection.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountCollection.cs @@ -8,10 +8,9 @@ using Microsoft.Extensions.Logging; using MongoDB.Driver; using NodaTime; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Tasks; -namespace Squidex.Domain.Apps.Entities.MongoDb; +namespace Squidex.Infrastructure.Counts; internal sealed class MongoCountCollection(IMongoDatabase database, ILogger log, string name) : MongoRepositoryBase(database) { diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountEntity.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountEntity.cs similarity index 93% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountEntity.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountEntity.cs index ff9cf2b5a5..8c2addc18e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCountEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Counts/MongoCountEntity.cs @@ -8,7 +8,7 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; -namespace Squidex.Domain.Apps.Entities.MongoDb; +namespace Squidex.Infrastructure.Counts; internal sealed class MongoCountEntity { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoHealthCheck.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/MongoHealthCheck.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoHealthCheck.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/MongoHealthCheck.cs diff --git a/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerCollection.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerCollection.cs new file mode 100644 index 0000000000..1d183f5022 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerCollection.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; + +#pragma warning disable CA1822 // Mark members as static + +namespace Squidex.Infrastructure.Diagnostics; + +public sealed class ProfilerCollection(IMongoDatabase database) +{ + private readonly IMongoCollection collection = database.GetCollection("system.profile"); + + public async Task> GetQueriesAsync(string collectionName, + CancellationToken ct = default) + { + var ns = $"{collection.Database.DatabaseNamespace.DatabaseName}.{collectionName}"; + + return await collection.Find(x => x.Operation == "query" && x.Namespace == ns).ToListAsync(ct); + } + + public async Task ClearAsync( + CancellationToken ct = default) + { + await database.RunCommandAsync("{ profile : 0 }", cancellationToken: ct); + await database.DropCollectionAsync(ProfilerDocument.CollectionName, ct); + await database.RunCommandAsync("{ profile : 2 }", cancellationToken: ct); + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerDocument.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerDocument.cs new file mode 100644 index 0000000000..1f14077741 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Diagnostics/ProfilerDocument.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Infrastructure.Diagnostics; + +[BsonIgnoreExtraElements] +public sealed class ProfilerDocument +{ + public const string CollectionName = "system.profile"; + + [BsonElement("op")] + public string Operation { get; set; } + + [BsonElement("ns")] + public string Namespace { get; set; } + + [BsonElement("nreturned")] + public int NumDocuments { get; set; } + + [BsonElement("keysExamined")] + public int KeysExamined { get; set; } + + [BsonElement("docsExamined")] + public int DocsExamined { get; set; } + + [BsonElement("planSummary")] + public string PlanSummary { get; set; } +} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Field.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Field.cs similarity index 96% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Field.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Field.cs index 2e11601392..c8bc22f8ea 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Field.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Field.cs @@ -7,7 +7,7 @@ using MongoDB.Bson.Serialization; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class Field { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/IVersionedEntity.cs similarity index 91% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/IVersionedEntity.cs index 067c06a1e2..a5f98e990c 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/IVersionedEntity.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/IVersionedEntity.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public interface IVersionedEntity { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequest.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Log/MongoRequest.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequest.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Log/MongoRequest.cs diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequestLogRepository.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Log/MongoRequestLogRepository.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequestLogRepository.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Log/MongoRequestLogRepository.cs index 5e86d805e6..837bbc89da 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/Log/MongoRequestLogRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Log/MongoRequestLogRepository.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Options; using MongoDB.Driver; using NodaTime; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.Log; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationEntity.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Migrations/MongoMigrationEntity.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationEntity.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Migrations/MongoMigrationEntity.cs diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Migrations/MongoMigrationStatus.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Migrations/MongoMigrationStatus.cs index e6bbb2919f..371d00b264 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Migrations/MongoMigrationStatus.cs @@ -6,7 +6,6 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.Migrations; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoBase.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoBase.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoBase.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/MongoBase.cs index ca67643b25..644dc8ebe5 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoBase.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoBase.cs @@ -10,7 +10,7 @@ #pragma warning disable RECS0108 // Warns about static fields in generic types -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public abstract class MongoBase { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoClientFactory.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoClientFactory.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoClientFactory.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/MongoClientFactory.cs index 2f96f8e5f2..d3ddc18171 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoClientFactory.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoClientFactory.cs @@ -9,7 +9,7 @@ using MongoDB.Driver.Linq; using Squidex.Infrastructure.Json.Objects; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class MongoClientFactory { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoDbErrorCodes.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoDbErrorCodes.cs similarity index 92% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoDbErrorCodes.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/MongoDbErrorCodes.cs index b974f2b255..8fb8cb4ddd 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoDbErrorCodes.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoDbErrorCodes.cs @@ -7,7 +7,7 @@ #pragma warning disable SA1310 // Field names should not contain underscore -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class MongoDbErrorCodes { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoExtensions.cs similarity index 99% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/MongoExtensions.cs index 0b964fc968..7ff2a0637c 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoExtensions.cs @@ -10,9 +10,10 @@ using System.Runtime.CompilerServices; using MongoDB.Bson; using MongoDB.Driver; +using Squidex.Infrastructure; using Squidex.Infrastructure.States; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public static class MongoExtensions { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoRepositoryBase.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/MongoRepositoryBase.cs index 2383447615..a47fe12e94 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/MongoRepositoryBase.cs @@ -10,7 +10,7 @@ using Squidex.Hosting; using Squidex.Hosting.Configuration; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public abstract class MongoRepositoryBase : MongoBase, IInitializable { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerCollection.cs similarity index 97% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerCollection.cs index 839740dbcb..80b413df8d 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerCollection.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerCollection.cs @@ -10,7 +10,7 @@ #pragma warning disable CA1822 // Mark members as static -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; public sealed class ProfilerCollection(IMongoDatabase database) { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerDocument.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerDocument.cs similarity index 95% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerDocument.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerDocument.cs index 2f8afb8f02..b3caa73e52 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/ProfilerDocument.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/ProfilerDocument.cs @@ -8,7 +8,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.Infrastructure; [BsonIgnoreExtraElements] public sealed class ProfilerDocument diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterBuilder.cs similarity index 93% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterBuilder.cs index eae715222d..883d7692b4 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterBuilder.cs @@ -6,10 +6,9 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Validation; -namespace Squidex.Infrastructure.MongoDb.Queries; +namespace Squidex.Infrastructure.Queries; public static class FilterBuilder { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterVisitor.cs similarity index 98% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterVisitor.cs index 33425c789d..acd91df6d2 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/FilterVisitor.cs @@ -9,9 +9,8 @@ using System.Text.RegularExpressions; using MongoDB.Bson; using MongoDB.Driver; -using Squidex.Infrastructure.Queries; -namespace Squidex.Infrastructure.MongoDb.Queries; +namespace Squidex.Infrastructure.Queries; public sealed class FilterVisitor : FilterNodeVisitor, ClrValue, None> { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/LimitExtensions.cs similarity index 95% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/LimitExtensions.cs index c6fea157af..becc31bd1b 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/LimitExtensions.cs @@ -6,9 +6,8 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Infrastructure.Queries; -namespace Squidex.Infrastructure.MongoDb.Queries; +namespace Squidex.Infrastructure.Queries; public static class LimitExtensions { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/SortDefinitionBuilder.cs similarity index 90% rename from backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/SortDefinitionBuilder.cs index dd230bdef5..1f52261aae 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/Queries/SortDefinitionBuilder.cs @@ -6,11 +6,10 @@ // ========================================================================== using MongoDB.Driver; -using Squidex.Infrastructure.Queries; -namespace Squidex.Infrastructure.MongoDb.Queries; +namespace Squidex.Infrastructure.Queries; -public static class SortBuilder +public static class SortDefinitionBuilder { public static SortDefinition? BuildSort(this ClrQuery query) { diff --git a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoSnapshotStore.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoSnapshotStore.cs diff --git a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoSnapshotStoreBase.cs similarity index 99% rename from backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoSnapshotStoreBase.cs index c8e686ed3d..773ed06efc 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStoreBase.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoSnapshotStoreBase.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoState.cs similarity index 95% rename from backend/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoState.cs index 9607192e9e..19ff1f14ff 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/States/MongoState.cs @@ -7,7 +7,6 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsage.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/UsageTracking/MongoUsage.cs similarity index 100% rename from backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsage.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/UsageTracking/MongoUsage.cs diff --git a/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs b/backend/src/Squidex.Data.MongoDb/Infrastructure/UsageTracking/MongoUsageRepository.cs similarity index 99% rename from backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs rename to backend/src/Squidex.Data.MongoDb/Infrastructure/UsageTracking/MongoUsageRepository.cs index cfdac6b28e..6437d60fd1 100644 --- a/backend/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageRepository.cs +++ b/backend/src/Squidex.Data.MongoDb/Infrastructure/UsageTracking/MongoUsageRepository.cs @@ -7,7 +7,6 @@ using MongoDB.Bson; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.UsageTracking; diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/AddAppIdToEventStream.cs b/backend/src/Squidex.Data.MongoDb/Migrations/AddAppIdToEventStream.cs new file mode 100644 index 0000000000..054f6bde53 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/AddAppIdToEventStream.cs @@ -0,0 +1,137 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics.CodeAnalysis; +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Migrations.MongoDb; + +public sealed class AddAppIdToEventStream(IMongoDatabase database) : MongoBase, IMigration +{ + public async Task UpdateAsync( + CancellationToken ct) + { + // Do not resolve in constructor, because most of the time it is not executed anyway. + var collectionV1 = database.GetCollection("Events"); + var collectionV2 = database.GetCollection("Events2"); + + // Run batch first, because it is cheaper as it has less items. + var batchedCommits = collectionV1.Find(FindAll).ToAsyncEnumerable(ct).Batch(500, ct).Buffered(2, ct); + + var options = new ParallelOptions + { + CancellationToken = ct, + // The tasks are mostly executed on database level, therefore we increase parallelism. + MaxDegreeOfParallelism = Environment.ProcessorCount * 2, + }; + + await Parallel.ForEachAsync(batchedCommits, ct, async (batch, ct) => + { + var writes = new List>(); + + foreach (var document in batch) + { + var eventStream = document["EventStream"].AsString; + + if (TryGetAppId(document, out var appId)) + { + if (!eventStream.StartsWith("app-", StringComparison.OrdinalIgnoreCase)) + { + var indexOfType = eventStream.IndexOf('-', StringComparison.Ordinal); + var indexOfId = indexOfType + 1; + + var indexOfOldId = eventStream.LastIndexOf("--", StringComparison.OrdinalIgnoreCase); + + if (indexOfOldId > 0) + { + indexOfId = indexOfOldId + 2; + } + + var domainType = eventStream[..indexOfType]; + var domainId = eventStream[indexOfId..]; + + var newDomainId = DomainId.Combine(DomainId.Create(appId), DomainId.Create(domainId)).ToString(); + var newStreamName = $"{domainType}-{newDomainId}"; + + document["EventStream"] = newStreamName; + + foreach (var @event in document["Events"].AsBsonArray) + { + var metadata = @event["Metadata"].AsBsonDocument; + + metadata["AggregateId"] = newDomainId; + } + } + + foreach (var @event in document["Events"].AsBsonArray) + { + var metadata = @event["Metadata"].AsBsonDocument; + + metadata.Remove("AppId"); + } + } + + var filter = Builders.Filter.Eq("_id", document["_id"].AsString); + + writes.Add(new ReplaceOneModel(filter, document) + { + IsUpsert = true + }); + } + + if (writes.Count > 0) + { + await collectionV2.BulkWriteAsync(writes, BulkUnordered, ct); + } + }); + } + + private static bool TryGetAppId(BsonDocument document, [MaybeNullWhen(false)] out string appId) + { + const int guidLength = 36; + + foreach (var @event in document["Events"].AsBsonArray) + { + var metadata = @event["Metadata"].AsBsonDocument; + + if (metadata.TryGetValue("AppId", out var value)) + { + appId = value.AsString; + return true; + } + + if (metadata.TryGetValue("AggregateId", out var aggregateId)) + { + var parts = aggregateId.AsString.Split("--"); + + if (parts.Length == 2) + { + appId = parts[0]; + return true; + } + } + + var payload = @event["Payload"].AsString; + + var indexOfAppId = payload.IndexOf("appId\":\"", StringComparison.OrdinalIgnoreCase); + + if (indexOfAppId > 0) + { + appId = payload.Substring(indexOfAppId, guidLength); + return true; + } + } + + appId = null; + + return false; + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/ConvertDocumentIds.cs b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertDocumentIds.cs new file mode 100644 index 0000000000..d9ebe2e0d4 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertDocumentIds.cs @@ -0,0 +1,130 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Migrations.MongoDb; + +public sealed class ConvertDocumentIds(IMongoDatabase databaseDefault, IMongoDatabase databaseContent) : MongoBase, IMigration +{ + private Scope scope; + + private enum Scope + { + None, + Assets, + Contents + } + + public override string ToString() + { + return $"{base.ToString()}({scope})"; + } + + public ConvertDocumentIds ForContents() + { + scope = Scope.Contents; + + return this; + } + + public ConvertDocumentIds ForAssets() + { + scope = Scope.Assets; + + return this; + } + + public async Task UpdateAsync( + CancellationToken ct) + { + switch (scope) + { + case Scope.Assets: + await RebuildAsync(databaseDefault, ConvertParentId, "States_Assets", ct); + await RebuildAsync(databaseDefault, ConvertParentId, "States_AssetFolders", ct); + break; + case Scope.Contents: + await RebuildAsync(databaseContent, null, "State_Contents_All", ct); + await RebuildAsync(databaseContent, null, "State_Contents_Published", ct); + break; + } + } + + private static async Task RebuildAsync(IMongoDatabase database, Action? extraAction, string collectionNameV1, + CancellationToken ct) + { + string collectionNameV2; + + collectionNameV2 = $"{collectionNameV1}2"; + collectionNameV2 = collectionNameV2.Replace("State_", "States_", StringComparison.Ordinal); + + // Do not resolve in constructor, because most of the time it is not executed anyway. + var collectionV1 = database.GetCollection(collectionNameV1); + var collectionV2 = database.GetCollection(collectionNameV2); + + if (!await collectionV1.AnyAsync(ct: ct)) + { + return; + } + + await collectionV2.DeleteManyAsync(FindAll, ct); + + // Run batch first, because it is cheaper as it has less items. + var batches = collectionV1.Find(FindAll).ToAsyncEnumerable(ct).Batch(500, ct).Buffered(2, ct); + + await Parallel.ForEachAsync(batches, ct, async (batch, ct) => + { + var writes = new List>(); + + foreach (var document in batch) + { + var appId = document["_ai"].AsString; + + var documentIdOld = document["_id"].AsString; + + if (documentIdOld.Contains("--", StringComparison.OrdinalIgnoreCase)) + { + var index = documentIdOld.LastIndexOf("--", StringComparison.OrdinalIgnoreCase); + + documentIdOld = documentIdOld[(index + 2)..]; + } + + var documentIdNew = DomainId.Combine(DomainId.Create(appId), DomainId.Create(documentIdOld)).ToString(); + + document["id"] = documentIdOld; + document["_id"] = documentIdNew; + + extraAction?.Invoke(document); + + var filter = Filter.Eq("_id", documentIdNew); + + writes.Add(new ReplaceOneModel(filter, document) + { + IsUpsert = true + }); + } + + if (writes.Count > 0) + { + await collectionV2.BulkWriteAsync(writes, BulkUnordered, ct); + } + }); + } + + private static void ConvertParentId(BsonDocument document) + { + if (document.Contains("pi")) + { + document["pi"] = document["pi"].AsGuid.ToString(); + } + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/ConvertOldSnapshotStores.cs b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertOldSnapshotStores.cs new file mode 100644 index 0000000000..a18d94bdf5 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertOldSnapshotStores.cs @@ -0,0 +1,32 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class ConvertOldSnapshotStores(IMongoDatabase database) : MongoBase, IMigration +{ + public Task UpdateAsync( + CancellationToken ct) + { + // Do not resolve in constructor, because most of the time it is not executed anyway. + var collections = new[] + { + "States_Apps", + "States_Rules", + "States_Schemas" + }.Select(x => database.GetCollection(x)); + + var update = Update.Rename("State", "Doc"); + + return Task.WhenAll(collections.Select(x => x.UpdateManyAsync(FindAll, update, cancellationToken: ct))); + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/ConvertRuleEventsJson.cs b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertRuleEventsJson.cs new file mode 100644 index 0000000000..195d3a8b3c --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/ConvertRuleEventsJson.cs @@ -0,0 +1,38 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class ConvertRuleEventsJson(IMongoDatabase database) : MongoBase, IMigration +{ + private readonly IMongoCollection collection = database.GetCollection("RuleEvents"); + + public async Task UpdateAsync( + CancellationToken ct) + { + foreach (var document in collection.Find(FindAll).ToEnumerable(ct)) + { + try + { + document["Job"]["actionData"] = document["Job"]["actionData"].ToBsonDocument().ToJson(); + + var filter = Filter.Eq("_id", document["_id"].ToString()); + + await collection.ReplaceOneAsync(filter, document, cancellationToken: ct); + } + catch + { + continue; + } + } + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/CopyRuleStatistics.cs b/backend/src/Squidex.Data.MongoDb/Migrations/CopyRuleStatistics.cs new file mode 100644 index 0000000000..a33e297286 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/CopyRuleStatistics.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class CopyRuleStatistics(IMongoDatabase database, IRuleUsageTracker ruleUsageTracker) : IMigration +{ + [BsonIgnoreExtraElements] + public class Document + { + public DomainId AppId { get; private set; } + + public DomainId RuleId { get; private set; } + + public int NumFailed { get; private set; } + + public int NumSucceeded { get; private set; } + } + + public async Task UpdateAsync( + CancellationToken ct) + { + var collectionName = "RuleStatistics"; + + // Do not create the collection if not needed. + if (!await database.CollectionExistsAsync(collectionName, ct)) + { + return; + } + + var collection = database.GetCollection(collectionName); + + await foreach (var document in collection.Find(new BsonDocument()).ToAsyncEnumerable(ct)) + { + await ruleUsageTracker.TrackAsync( + document.AppId, + document.RuleId, + default, + 0, + document.NumSucceeded, + document.NumFailed, + ct); + } + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/DeleteContentCollections.cs b/backend/src/Squidex.Data.MongoDb/Migrations/DeleteContentCollections.cs new file mode 100644 index 0000000000..ccaedf40f1 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/DeleteContentCollections.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Driver; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class DeleteContentCollections(IMongoDatabase database) : IMigration +{ + public async Task UpdateAsync( + CancellationToken ct) + { + await database.DropCollectionAsync("States_Contents", ct); + await database.DropCollectionAsync("States_Contents_Archive", ct); + await database.DropCollectionAsync("State_Content_Draft", ct); + await database.DropCollectionAsync("State_Content_Published", ct); + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetMetadata.cs b/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetMetadata.cs new file mode 100644 index 0000000000..c8cb0e6a98 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetMetadata.cs @@ -0,0 +1,53 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class RenameAssetMetadata(IMongoDatabase database) : MongoBase, IMigration +{ + public async Task UpdateAsync( + CancellationToken ct) + { + // Do not resolve in constructor, because most of the time it is not executed anyway. + var collection = database.GetCollection("States_Assets"); + + // Create metadata. + await collection.UpdateManyAsync(FindAll, + Update.Set("md", new BsonDocument()), + cancellationToken: ct); + + // Remove null pixel infos. + await collection.UpdateManyAsync(new BsonDocument("ph", BsonValue.Create(null)), + Update.Unset("ph").Unset("pw"), + cancellationToken: ct); + + // Set pixel metadata. + await collection.UpdateManyAsync(FindAll, + Update.Rename("ph", "md.pixelHeight").Rename("pw", "md.pixelWidth"), + cancellationToken: ct); + + // Set type to image. + await collection.UpdateManyAsync(new BsonDocument("im", true), + Update.Set("at", "Image"), + cancellationToken: ct); + + // Set type to unknown. + await collection.UpdateManyAsync(new BsonDocument("im", false), + Update.Set("at", "Unknown"), + cancellationToken: ct); + + // Remove IsImage. + await collection.UpdateManyAsync(FindAll, + Update.Unset("im"), + cancellationToken: ct); + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetSlugField.cs b/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetSlugField.cs new file mode 100644 index 0000000000..2c0b453951 --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/RenameAssetSlugField.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class RenameAssetSlugField(IMongoDatabase database) : MongoBase, IMigration +{ + public Task UpdateAsync( + CancellationToken ct) + { + // Do not resolve in constructor, because most of the time it is not executed anyway. + var collection = database.GetCollection("States_Assets"); + + var update = Builders.Update.Rename("FileNameSlug", "Slug"); + + return collection.UpdateManyAsync(FindAll, update, cancellationToken: ct); + } +} diff --git a/backend/src/Squidex.Data.MongoDb/Migrations/RestructureContentCollection.cs b/backend/src/Squidex.Data.MongoDb/Migrations/RestructureContentCollection.cs new file mode 100644 index 0000000000..d6dcd098aa --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/Migrations/RestructureContentCollection.cs @@ -0,0 +1,35 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; + +namespace Squidex.Migrations.MongoDb; + +public sealed class RestructureContentCollection(IMongoDatabase contentDatabase) : MongoBase, IMigration +{ + public async Task UpdateAsync( + CancellationToken ct) + { + if (await contentDatabase.CollectionExistsAsync("State_Content_Draft", ct)) + { + await contentDatabase.DropCollectionAsync("State_Contents", ct); + await contentDatabase.DropCollectionAsync("State_Content_Published", ct); + + await contentDatabase.RenameCollectionAsync("State_Content_Draft", "State_Contents", cancellationToken: ct); + } + + if (await contentDatabase.CollectionExistsAsync("State_Contents", ct)) + { + var collection = contentDatabase.GetCollection("State_Contents"); + + await collection.UpdateManyAsync(FindAll, Update.Unset("dt"), cancellationToken: ct); + } + } +} diff --git a/backend/src/Squidex.Data.MongoDb/ServiceExtensions.cs b/backend/src/Squidex.Data.MongoDb/ServiceExtensions.cs new file mode 100644 index 0000000000..3321261c8f --- /dev/null +++ b/backend/src/Squidex.Data.MongoDb/ServiceExtensions.cs @@ -0,0 +1,297 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Net; +using System.Text.Json; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; +using MongoDB.Driver.Core.Extensions.DiagnosticSources; +using MongoDB.Driver.GridFS; +using Squidex.AI; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Core.Teams; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Apps.Repositories; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Text; +using Squidex.Domain.Apps.Entities.Contents.Text.State; +using Squidex.Domain.Apps.Entities.History; +using Squidex.Domain.Apps.Entities.History.Repositories; +using Squidex.Domain.Apps.Entities.MongoDb.Assets; +using Squidex.Domain.Apps.Entities.MongoDb.Contents; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Repositories; +using Squidex.Domain.Apps.Entities.Teams; +using Squidex.Domain.Apps.Entities.Teams.Repositories; +using Squidex.Domain.Apps.Entities.Text; +using Squidex.Domain.Users; +using Squidex.Domain.Users.InMemory; +using Squidex.Domain.Users.MongoDb; +using Squidex.Events; +using Squidex.Events.Mongo; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Caching; +using Squidex.Infrastructure.Diagnostics; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.States; +using Squidex.Infrastructure.UsageTracking; +using Squidex.Migrations.MongoDb; +using YDotNet.Server.MongoDB; + +namespace Squidex; + +public static class ServiceExtensions +{ + public static void AddSquidexMongoEventStore(this IServiceCollection services, IConfiguration config) + { + var mongoConfiguration = config.GetRequiredValue("eventStore:mongoDb:configuration"); + var mongoDatabaseName = config.GetRequiredValue("eventStore:mongoDb:database"); + + services.AddMongoEventStore(config); + services.AddSingletonAs(c => + { + var options = c.GetRequiredService>(); + var mongoClient = GetMongoClient(mongoConfiguration); + var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); + + return new MongoEventStore(mongoDatabase, options); + }) + .As(); + } + + public static void AddSquidexMongoAssetStore(this IServiceCollection services, IConfiguration config) + { + var mongoConfiguration = config.GetRequiredValue("assetStore:mongoDb:configuration"); + var mongoDatabaseName = config.GetRequiredValue("assetStore:mongoDb:database"); + var mongoGridFsBucketName = config.GetRequiredValue("assetStore:mongoDb:bucket"); + + services.AddMongoAssetStore(c => + { + var mongoClient = GetMongoClient(mongoConfiguration); + var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); + + return new GridFSBucket(mongoDatabase, new GridFSBucketOptions + { + BucketName = mongoGridFsBucketName + }); + }); + } + + public static void AddSquidexMongoStore(this IServiceCollection services, IConfiguration config) + { + var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration")!; + var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database")!; + var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName)!; + + services.AddMongoAssetKeyValueStore(); + services.AddSingleton(typeof(ISnapshotStore<>), typeof(MongoSnapshotStore<>)); + + services.AddYDotNet() + .AddMongoStorage(options => + { + options.DatabaseName = mongoDatabaseName; + }); + + services.AddAI() + .AddMongoChatStore(config, options => + { + options.CollectionName = "Chat"; + }); + + services.AddMessaging() + .AddMongoDataStore(config); + + services.AddSingletonAs(c => GetMongoClient(mongoConfiguration)) + .As(); + + services.AddSingletonAs(c => GetDatabase(c, mongoDatabaseName)) + .As(); + + services.AddSingletonAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs(c => new DeleteContentCollections(GetDatabase(c, mongoContentDatabaseName))) + .As(); + + services.AddTransientAs(c => new RestructureContentCollection(GetDatabase(c, mongoContentDatabaseName))) + .As(); + + services.AddTransientAs(c => new ConvertDocumentIds(GetDatabase(c, mongoDatabaseName), GetDatabase(c, mongoContentDatabaseName))) + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddHealthChecks() + .AddCheck("MongoDB", tags: ["node"]); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As().As(); + + services.AddSingletonAs() + .As().As(); + + services.AddSingletonAs() + .As>(); + + services.AddSingletonAs() + .As>().As(); + + services.AddSingletonAs() + .As().As>().As(); + + services.AddSingletonAs() + .As().As>().As(); + + services.AddSingletonAs() + .As().As>(); + + services.AddSingletonAs() + .As().As>().As(); + + services.AddSingletonAs() + .As().As>().As(); + + services.AddSingletonAs() + .AsOptional().As().As(); + + services.AddSingletonAs() + .As().As(); + + services.AddSingletonAs(c => + { + return new MongoShardedAssetRepository(GetSharding(config, "store:mongoDB:assetShardCount"), + shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); + }).As().As>().As(); + + services.AddSingletonAs(c => + { + var contentDatabase = GetDatabase(c, mongoContentDatabaseName); + + return new MongoShardedContentRepository(GetSharding(config, "store:mongoDB:contentShardCount"), + shardKey => ActivatorUtilities.CreateInstance(c, shardKey, contentDatabase)); + }).As().As>().As(); + + services.AddOpenIddict() + .AddCore(builder => + { + builder.UseMongoDb() + .SetScopesCollectionName("Identity_Scopes") + .SetTokensCollectionName("Identity_Tokens"); + + builder.SetDefaultScopeEntity(); + builder.SetDefaultApplicationEntity(); + }); + + var atlasOptions = config.GetSection("store:mongoDb:atlas").Get() ?? new (); + + if (atlasOptions.IsConfigured() && atlasOptions.FullTextEnabled) + { + services.Configure(config.GetSection("store:mongoDb:atlas")); + + services.AddHttpClient("Atlas", options => + { + options.BaseAddress = new Uri("https://cloud.mongodb.com/"); + }) + .ConfigurePrimaryHttpMessageHandler(() => + { + return new HttpClientHandler + { + Credentials = new NetworkCredential(atlasOptions.PublicKey, atlasOptions.PrivateKey, "cloud.mongodb.com") + }; + }); + + services.AddSingletonAs(c => + { + return new MongoShardedTextIndex>(GetSharding(config, "store:mongoDB:textShardCount"), + shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); + }).AsOptional().As(); + } + else + { + services.AddSingletonAs(c => + { + return new MongoShardedTextIndex>(GetSharding(config, "store:mongoDB:textShardCount"), + shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); + }).AsOptional().As(); + } + + services.AddInitializer("Serializer (BSON)", jsonSerializerOptions => + { + var representation = config.GetValue("store:mongoDB:valueRepresentation"); + + BsonJsonConvention.Register(jsonSerializerOptions, representation); + }, int.MinValue); + } + + private static IMongoClient GetMongoClient(string configuration) + { + return Singletons.GetOrAdd(configuration, connectionString => + { + return MongoClientFactory.Create(connectionString, settings => + { + settings.ClusterConfigurator = builder => + { + builder.Subscribe(new DiagnosticsActivityEventSubscriber()); + }; + }); + }); + } + + private static IShardingStrategy GetSharding(IConfiguration config, string name) + { + var shardCount = config.GetValue(name); + + return shardCount > 0 && shardCount <= 100 ? new PartitionedSharding(shardCount) : SingleSharding.Instance; + } + + private static IMongoDatabase GetDatabase(IServiceProvider serviceProvider, string name) + { + return serviceProvider.GetRequiredService().GetDatabase(name); + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj b/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj similarity index 61% rename from backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj rename to backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj index 988e72b0ac..06bf6d5095 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj +++ b/backend/src/Squidex.Data.MongoDb/Squidex.Data.MongoDb.csproj @@ -1,6 +1,7 @@ - + net8.0 + Squidex latest enable enable @@ -10,12 +11,9 @@ True - - - - - + + @@ -24,9 +22,18 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs index 953334eec7..bffed50c50 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs @@ -22,7 +22,7 @@ public async Task EnrichAsync(EnrichedEvent enrichedEvent, Envelope? @ { if (@event != null) { - enrichedEvent.Timestamp = @event.Headers.Timestamp(); + enrichedEvent.Timestamp = @event.Headers.TimestampAsInstant(); enrichedEvent.AppId = @event.Payload.AppId; } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index bada0828e0..9d050df606 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -170,7 +170,7 @@ private async IAsyncEnumerable CreateJobs(Envelope @event, Ru var eventTime = @event.Headers.ContainsKey(CommonHeaders.Timestamp) ? - @event.Headers.Timestamp() : + @event.Headers.TimestampAsInstant() : now; if (!context.IncludeStale && eventTime.Plus(Constants.StaleTime) < now) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index 5436744555..71c4d400bc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Subscriptions/SubscriptionPublisher.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Subscriptions/SubscriptionPublisher.cs index e2c81847da..c22ce994c6 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Subscriptions/SubscriptionPublisher.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Subscriptions/SubscriptionPublisher.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Messaging.Subscriptions; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs index f99f9bb537..ffdeed3266 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppEventDeleter.cs @@ -6,7 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Events; namespace Squidex.Domain.Apps.Entities.Apps; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs index 9ddfc3cdf1..879166abe4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/AppPermanentDeleter.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Events.Apps; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs index efde8234eb..ba49159b26 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs @@ -7,6 +7,7 @@ using Squidex.Assets; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs index dd9af405b8..bb02da189d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs index 910f2c68a9..931c7641df 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/RebuildFiles.cs @@ -8,6 +8,7 @@ using System.Text; using Squidex.Assets; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs index b1cc8c4c82..487e65ec4b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/RecursiveDeleter.cs @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupJob.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupJob.cs index c75e06d9a6..ae01b1fd3a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupJob.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupJob.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Jobs; using Squidex.Domain.Apps.Events; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Translations; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs index e3faa5e45a..e6c7b8c23d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs @@ -8,8 +8,8 @@ using System.IO.Compression; using Squidex.Domain.Apps.Entities.Backup.Helpers; using Squidex.Domain.Apps.Entities.Backup.Model; +using Squidex.Events; using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Entities.Backup; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupWriter.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupWriter.cs index b2347119ef..e46b009a24 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupWriter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupWriter.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.EventSourcing; +using Squidex.Events; namespace Squidex.Domain.Apps.Entities.Backup; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs index 5a0f36f0fe..672ca37008 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs @@ -6,7 +6,7 @@ // ========================================================================== using System.Text.Json.Serialization; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Events; using Squidex.Infrastructure.Json.System; #pragma warning disable MA0048 // File name must match type name @@ -35,7 +35,7 @@ public static CompatibleStoredEvent V1(StoredEvent stored) return new CompatibleStoredEvent { Data = CompatibleEventData.V1(stored.Data), - EventPosition = stored.EventPosition, + EventPosition = stored.EventPosition.Token!, EventStreamNumber = stored.EventStreamNumber, StreamName = stored.StreamName }; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs index e883df84ab..31bf2748b2 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Entities.Jobs; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; @@ -265,7 +266,7 @@ private async Task ReadEventsAsync(JobRunContext run, State state, { var commits = batch.Select(item => - EventCommit.Create( + EventCommitBuilder.Create( item.Stream, item.Offset, item.Event, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs index 2d476b053f..33005c0c31 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Collaboration/CommentCollaborationHandler.cs @@ -10,6 +10,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Comments; using Squidex.Domain.Apps.Events.Comments; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs index d04a401a8d..98e3d05669 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentEventDeleter.cs @@ -8,8 +8,8 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Events; using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Contents; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs index 9c992341d6..f2d1a0c768 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexingProcess.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; diff --git a/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs index dad751e51d..81b8e92140 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/EntityExtensions.cs @@ -17,7 +17,7 @@ public static T Apply(this T source, Envelope @event) where T : { var headers = @event.Headers; - var timestamp = headers.Timestamp(); + var timestamp = headers.TimestampAsInstant(); var created = source.Created; var createdBy = source.CreatedBy; diff --git a/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs b/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs index c61010a6a1..adf0d96b8e 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/History/HistoryService.cs @@ -5,10 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using NodaTime.Extensions; using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Comments; using Squidex.Domain.Apps.Events.Teams; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; @@ -113,7 +115,7 @@ public async Task On(IEnumerable> events) { historyEvent.Actor = actor; historyEvent.OwnerId = ownerId; - historyEvent.Created = @event.Headers.Timestamp(); + historyEvent.Created = @event.Headers.Timestamp().ToInstant(); historyEvent.Version = @event.Headers.EventStreamNumber(); return historyEvent; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs index c6f056f9b8..73acd79233 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/History/NotifoService.cs @@ -153,7 +153,7 @@ public async Task HandleEventsAsync(IEnumerable<(Envelope AppEvent, Hist var batches = events .Where(x => x.AppEvent.Headers.Restored() == false) - .Where(x => x.AppEvent.Headers.Timestamp() > maxAge) + .Where(x => x.AppEvent.Headers.TimestampAsInstant() > maxAge) .SelectMany(x => CreateRequests(x.AppEvent, x.HistoryEvent)) .Batch(50); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs b/backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs index a79d14e67c..45854d0bbb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Invitation/InvitationEventConsumer.cs @@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.Collaboration; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Teams; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Shared.Users; @@ -41,11 +42,10 @@ public async Task On(Envelope @event) return; } - var now = SystemClock.Instance.GetCurrentInstant(); + var timestamp = @event.Headers.TimestampAsInstant(); - var timestamp = @event.Headers.Timestamp(); - - if (now - timestamp > MaxAge) + var currentTime = SystemClock.Instance.GetCurrentInstant(); + if (currentTime - timestamp > MaxAge) { return; } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs index 0d2ed42e02..d4ceba59fc 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/DefaultRuleRunnerService.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Jobs; using Squidex.Domain.Apps.Events; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.EventSourcing; @@ -51,7 +52,7 @@ public async Task> SimulateAsync(NamedId appI var simulatedEvents = new List(MaxSimulatedEvents); - var streamStart = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7)); + var streamStart = SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromDays(7)).ToDateTimeUtc(); var streamFilter = StreamFilter.Prefix($"([a-zA-Z0-9]+)-{appId.Id}"); await foreach (var storedEvent in eventStore.QueryAllReverseAsync(streamFilter, streamStart, MaxSimulatedEvents, ct)) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs index ccac502e83..0d3b642987 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/Runner/RuleRunnerJob.cs @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Jobs; using Squidex.Domain.Apps.Entities.Rules.Repositories; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Translations; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs index 988c7797dc..cd326485fb 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/SchemaPermanentDeleter.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Entities.Schemas.DomainObject; using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; @@ -60,7 +61,7 @@ private async Task OnDeleteAsync(SchemaDeleted schemaDeleted) return; } - using var activity = Telemetry.Activities.StartActivity("RemoveAppFromSystem"); + using var activity = Infrastructure.Telemetry.Activities.StartActivity("RemoveAppFromSystem"); var app = await appProvider.GetAppAsync(schemaDeleted.AppId.Id); if (app == null) @@ -76,7 +77,7 @@ private async Task OnDeleteAsync(SchemaDeleted schemaDeleted) foreach (var deleter in deleters) { - using (Telemetry.Activities.StartActivity(deleter.GetType().Name)) + using (Infrastructure.Telemetry.Activities.StartActivity(deleter.GetType().Name)) { await deleter.DeleteSchemaAsync(app, schema.Snapshot, default); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index c895f072a6..01282f3231 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -34,7 +34,7 @@ - + diff --git a/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj deleted file mode 100644 index 9bf54c0836..0000000000 --- a/backend/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - net8.0 - latest - enable - enable - - - full - True - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.Azure/Diagnostics/CosmosDbHealthCheck.cs b/backend/src/Squidex.Infrastructure.Azure/Diagnostics/CosmosDbHealthCheck.cs deleted file mode 100644 index 9625c95711..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/Diagnostics/CosmosDbHealthCheck.cs +++ /dev/null @@ -1,33 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Documents.Client; -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace Squidex.Infrastructure.Diagnostics -{ - public sealed class CosmosDbHealthCheck : IHealthCheck - { - private readonly DocumentClient documentClient; - - public CosmosDbHealthCheck(Uri uri, string masterKey) - { - documentClient = new DocumentClient(uri, masterKey); - } - - public async Task CheckHealthAsync(HealthCheckContext context, - CancellationToken cancellationToken = default) - { - await documentClient.ReadDatabaseFeedAsync(); - - return HealthCheckResult.Healthy("Application must query data from CosmosDB."); - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/Constants.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/Constants.cs deleted file mode 100644 index 120f387045..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/Constants.cs +++ /dev/null @@ -1,16 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing -{ - internal static class Constants - { - public const string Collection = "Events"; - - public const string LeaseCollection = "Leases"; - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs deleted file mode 100644 index cef83bb182..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEvent.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing -{ - internal sealed class CosmosDbEvent - { - public string Type { get; set; } - - public string Payload { get; set; } - - public EnvelopeHeaders Headers { get; set; } - - public static CosmosDbEvent FromEventData(EventData data) - { - return new CosmosDbEvent { Type = data.Type, Headers = data.Headers, Payload = data.Payload }; - } - - public EventData ToEventData() - { - return new EventData(Type, Headers, Payload); - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs deleted file mode 100644 index 194ad925eb..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventCommit.cs +++ /dev/null @@ -1,26 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Infrastructure.EventSourcing -{ - internal sealed class CosmosDbEventCommit - { - public Guid Id { get; set; } - - public CosmosDbEvent[] Events { get; set; } - - public long EventStreamOffset { get; set; } - - public long EventsCount { get; set; } - - public string EventStream { get; set; } - - public long Timestamp { get; set; } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs deleted file mode 100644 index f756ca0b22..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore.cs +++ /dev/null @@ -1,124 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.ObjectModel; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Squidex.Hosting; -using Squidex.Infrastructure.Json; -using Index = Microsoft.Azure.Documents.Index; - -namespace Squidex.Infrastructure.EventSourcing -{ - public sealed partial class CosmosDbEventStore : DisposableObjectBase, IEventStore, IInitializable - { - private readonly DocumentClient documentClient; - private readonly Uri collectionUri; - private readonly Uri databaseUri; - - public IJsonSerializer JsonSerializer { get; } - - public string DatabaseId { get; } - - public string MasterKey { get; } - - public Uri ServiceUri - { - get => documentClient.ServiceEndpoint; - } - - public CosmosDbEventStore(DocumentClient documentClient, string masterKey, string database, IJsonSerializer jsonSerializer) - { - this.documentClient = documentClient; - - databaseUri = UriFactory.CreateDatabaseUri(database); - DatabaseId = database; - - collectionUri = UriFactory.CreateDocumentCollectionUri(database, Constants.Collection); - - MasterKey = masterKey; - - JsonSerializer = jsonSerializer; - } - - protected override void DisposeObject(bool disposing) - { - if (disposing) - { - documentClient.Dispose(); - } - } - - public async Task InitializeAsync( - CancellationToken ct = default) - { - await documentClient.CreateDatabaseIfNotExistsAsync(new Database { Id = DatabaseId }); - - await documentClient.CreateDocumentCollectionIfNotExistsAsync(databaseUri, - new DocumentCollection - { - PartitionKey = new PartitionKeyDefinition - { - Paths = new Collection - { - "/id" - } - }, - Id = Constants.LeaseCollection - }); - - await documentClient.CreateDocumentCollectionIfNotExistsAsync(databaseUri, - new DocumentCollection - { - PartitionKey = new PartitionKeyDefinition - { - Paths = new Collection - { - "/eventStream" - } - }, - IndexingPolicy = new IndexingPolicy - { - IncludedPaths = new Collection - { - new IncludedPath - { - Path = "/*", - Indexes = new Collection - { - Index.Range(DataType.Number), - Index.Range(DataType.String) - } - } - } - }, - UniqueKeyPolicy = new UniqueKeyPolicy - { - UniqueKeys = new Collection - { - new UniqueKey - { - Paths = new Collection - { - "/eventStream", - "/eventStreamOffset" - } - } - } - }, - Id = Constants.Collection - }, - new RequestOptions - { - PartitionKey = new PartitionKey("/eventStream") - }); - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs deleted file mode 100644 index b498884d27..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Reader.cs +++ /dev/null @@ -1,166 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Squidex.Hosting; -using Squidex.Log; - -namespace Squidex.Infrastructure.EventSourcing -{ - public delegate bool EventPredicate(EventData data); - - public partial class CosmosDbEventStore : IEventStore, IInitializable - { - private static readonly IReadOnlyList EmptyEvents = new List(); - - public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string? streamFilter = null, string? position = null) - { - Guard.NotNull(subscriber, nameof(subscriber)); - - ThrowIfDisposed(); - - return new CosmosDbSubscription(this, subscriber, streamFilter, position); - } - - public async Task> QueryLatestAsync(string streamName, int count) - { - Guard.NotNullOrEmpty(streamName, nameof(streamName)); - - ThrowIfDisposed(); - - if (count <= 0) - { - return EmptyEvents; - } - - using (Profiler.TraceMethod()) - { - var query = FilterBuilder.ByStreamNameDesc(streamName, count); - - var result = new List(); - - await foreach (var commit in documentClient.QueryAsync(collectionUri, query, default)) - { - foreach (var storedEvent in commit.Filtered().Reverse()) - { - result.Add(storedEvent); - - if (result.Count == count) - { - break; - } - } - } - - return result; - } - } - - public async Task> QueryAsync(string streamName, long streamPosition = 0) - { - Guard.NotNullOrEmpty(streamName, nameof(streamName)); - - ThrowIfDisposed(); - - using (Profiler.TraceMethod()) - { - var query = FilterBuilder.ByStreamName(streamName, streamPosition - MaxCommitSize); - - var result = new List(); - - await foreach (var commit in documentClient.QueryAsync(collectionUri, query, default)) - { - foreach (var storedEvent in commit.Filtered().Reverse()) - { - result.Add(storedEvent); - } - } - - return result; - } - } - - public async IAsyncEnumerable QueryAllAsync( string? streamFilter = null, string? position = null, long take = long.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - ThrowIfDisposed(); - - if (take <= 0) - { - yield break; - } - - StreamPosition lastPosition = position; - - var filterDefinition = FilterBuilder.CreateByFilter(streamFilter, lastPosition, "ASC", take); - - var taken = int.MaxValue; - - await foreach (var commit in documentClient.QueryAsync(collectionUri, filterDefinition, ct: ct)) - { - if (taken == take) - { - yield break; - } - - foreach (var storedEvent in commit.Filtered(lastPosition)) - { - if (taken == take) - { - yield break; - } - - yield return storedEvent; - - taken++; - } - } - } - - public async IAsyncEnumerable QueryAllReverseAsync(string? streamFilter = null, string? position = null, long take = long.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - ThrowIfDisposed(); - - if (take <= 0) - { - yield break; - } - - StreamPosition lastPosition = position; - - var filterDefinition = FilterBuilder.CreateByFilter(streamFilter, lastPosition, "DESC", take); - - var taken = long.MaxValue; - - await foreach (var commit in documentClient.QueryAsync(collectionUri, filterDefinition, ct: ct)) - { - if (taken == take) - { - yield break; - } - - foreach (var storedEvent in commit.Filtered(lastPosition)) - { - if (taken == take) - { - yield break; - } - - yield return storedEvent; - - taken++; - } - } - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs deleted file mode 100644 index 68bf3d0d57..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbEventStore_Writer.cs +++ /dev/null @@ -1,154 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using NodaTime; -using Squidex.Log; - -namespace Squidex.Infrastructure.EventSourcing -{ - public partial class CosmosDbEventStore - { - private const int MaxWriteAttempts = 20; - private const int MaxCommitSize = 10; - - public async Task DeleteStreamAsync(string streamName) - { - Guard.NotNullOrEmpty(streamName, nameof(streamName)); - - ThrowIfDisposed(); - - var query = FilterBuilder.AllIds(streamName); - - var deleteOptions = new RequestOptions - { - PartitionKey = new PartitionKey(streamName) - }; - - await foreach (var commit in documentClient.QueryAsync(collectionUri, query)) - { - var documentUri = UriFactory.CreateDocumentUri(DatabaseId, Constants.Collection, commit.Id.ToString()); - - await documentClient.DeleteDocumentAsync(documentUri, deleteOptions); - } - } - - public Task AppendAsync(Guid commitId, string streamName, ICollection events) - { - return AppendAsync(commitId, streamName, EtagVersion.Any, events); - } - - public async Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection events) - { - Guard.NotEmpty(commitId, nameof(commitId)); - Guard.NotNullOrEmpty(streamName, nameof(streamName)); - Guard.NotNull(events, nameof(events)); - Guard.LessThan(events.Count, MaxCommitSize, "events.Count"); - - ThrowIfDisposed(); - - using (Profiler.TraceMethod()) - { - if (events.Count == 0) - { - return; - } - - var currentVersion = await GetEventStreamOffsetAsync(streamName); - - if (expectedVersion > EtagVersion.Any && expectedVersion != currentVersion) - { - throw new WrongEventVersionException(currentVersion, expectedVersion); - } - - var commit = BuildCommit(commitId, streamName, expectedVersion >= -1 ? expectedVersion : currentVersion, events); - - for (var attempt = 0; attempt < MaxWriteAttempts; attempt++) - { - try - { - await documentClient.CreateDocumentAsync(collectionUri, commit); - - return; - } - catch (DocumentClientException ex) - { - if (ex.StatusCode == HttpStatusCode.Conflict) - { - currentVersion = await GetEventStreamOffsetAsync(streamName); - - if (expectedVersion > EtagVersion.Any) - { - throw new WrongEventVersionException(currentVersion, expectedVersion); - } - - if (attempt < MaxWriteAttempts) - { - expectedVersion = currentVersion; - } - else - { - throw new TimeoutException("Could not acquire a free slot for the commit within the provided time."); - } - } - else - { - throw; - } - } - } - } - } - - private async Task GetEventStreamOffsetAsync(string streamName) - { - var query = - documentClient.CreateDocumentQuery(collectionUri, - FilterBuilder.LastPosition(streamName)); - - var document = await query.FirstOrDefaultAsync(); - - if (document != null) - { - return document.EventStreamOffset + document.EventsCount; - } - - return EtagVersion.Empty; - } - - private static CosmosDbEventCommit BuildCommit(Guid commitId, string streamName, long expectedVersion, ICollection events) - { - var commitEvents = new CosmosDbEvent[events.Count]; - - var i = 0; - - foreach (var e in events) - { - var mongoEvent = CosmosDbEvent.FromEventData(e); - - commitEvents[i++] = mongoEvent; - } - - var mongoCommit = new CosmosDbEventCommit - { - Id = commitId, - Events = commitEvents, - EventsCount = events.Count, - EventStream = streamName, - EventStreamOffset = expectedVersion, - Timestamp = SystemClock.Instance.GetCurrentInstant().ToUnixTimeTicks() - }; - - return mongoCommit; - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs deleted file mode 100644 index 97ff67e3d1..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/CosmosDbSubscription.cs +++ /dev/null @@ -1,143 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.ChangeFeedProcessor.FeedProcessing; -using Builder = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorBuilder; -using Collection = Microsoft.Azure.Documents.ChangeFeedProcessor.DocumentCollectionInfo; -using Options = Microsoft.Azure.Documents.ChangeFeedProcessor.ChangeFeedProcessorOptions; - -#pragma warning disable IDE0017 // Simplify object initialization - -namespace Squidex.Infrastructure.EventSourcing -{ - internal sealed class CosmosDbSubscription : IEventSubscription, IChangeFeedObserverFactory, IChangeFeedObserver - { - private readonly TaskCompletionSource processorStopRequested = new TaskCompletionSource(); - private readonly CosmosDbEventStore store; - private readonly Regex regex; - private readonly string hostName; - private readonly IEventSubscriber subscriber; - - public CosmosDbSubscription(CosmosDbEventStore store, IEventSubscriber subscriber, string? streamFilter, string? position = null) - { - this.store = store; - - var fromBeginning = string.IsNullOrWhiteSpace(position); - - if (fromBeginning) - { - hostName = $"squidex.{DateTime.UtcNow.Ticks}"; - } - else - { - hostName = position ?? "none"; - } - - if (!StreamFilter.IsAll(streamFilter)) - { - regex = new Regex(streamFilter); - } - - this.subscriber = subscriber; - - Task.Run(async () => - { - try - { - var processor = - await new Builder() - .WithFeedCollection(CreateCollection(store, Constants.Collection)) - .WithLeaseCollection(CreateCollection(store, Constants.LeaseCollection)) - .WithHostName(hostName) - .WithProcessorOptions(new Options { StartFromBeginning = fromBeginning, LeasePrefix = hostName }) - .WithObserverFactory(this) - .BuildAsync(); - - await processor.StartAsync(); - await processorStopRequested.Task; - await processor.StopAsync(); - } - catch (Exception ex) - { - await subscriber.OnErrorAsync(this, ex); - } - }); - } - - private static Collection CreateCollection(CosmosDbEventStore store, string name) - { - var collection = new Collection(); - - collection.CollectionName = name; - collection.DatabaseName = store.DatabaseId; - collection.MasterKey = store.MasterKey; - collection.Uri = store.ServiceUri; - - return collection; - } - - public IChangeFeedObserver CreateObserver() - { - return this; - } - - public async Task CloseAsync(IChangeFeedObserverContext context, ChangeFeedObserverCloseReason reason) - { - if (reason == ChangeFeedObserverCloseReason.ObserverError) - { - await subscriber.OnErrorAsync(this, new InvalidOperationException("Change feed observer failed.")); - } - } - - public Task OpenAsync(IChangeFeedObserverContext context) - { - return Task.CompletedTask; - } - - public async Task ProcessChangesAsync(IChangeFeedObserverContext context, IReadOnlyList docs, - CancellationToken cancellationToken) - { - if (!processorStopRequested.Task.IsCompleted) - { - foreach (var document in docs) - { - if (!processorStopRequested.Task.IsCompleted) - { - var streamName = document.GetPropertyValue("eventStream"); - - if (regex == null || regex.IsMatch(streamName)) - { - var commit = store.JsonSerializer.Deserialize(document.ToString()); - - var eventStreamOffset = (int)commit.EventStreamOffset; - - foreach (var @event in commit.Events) - { - eventStreamOffset++; - - var eventData = @event.ToEventData(); - - await subscriber.OnEventAsync(this, new StoredEvent(commit.EventStream, hostName, eventStreamOffset, eventData)); - } - } - } - } - } - } - - public void Unsubscribe() - { - processorStopRequested.SetResult(true); - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterBuilder.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterBuilder.cs deleted file mode 100644 index f24d50f8c3..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterBuilder.cs +++ /dev/null @@ -1,139 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using Microsoft.Azure.Documents; - -namespace Squidex.Infrastructure.EventSourcing -{ - internal static class FilterBuilder - { - public static SqlQuerySpec AllIds(string streamName) - { - var query = - $"SELECT TOP 1 " + - $" e.id," + - $" e.eventsCount " + - $"FROM {Constants.Collection} e " + - $"WHERE " + - $" e.eventStream = @name " + - $"ORDER BY e.eventStreamOffset DESC"; - - var parameters = new SqlParameterCollection - { - new SqlParameter("@name", streamName) - }; - - return new SqlQuerySpec(query, parameters); - } - - public static SqlQuerySpec LastPosition(string streamName) - { - var query = - $"SELECT TOP 1 " + - $" e.eventStreamOffset," + - $" e.eventsCount " + - $"FROM {Constants.Collection} e " + - $"WHERE " + - $" e.eventStream = @name " + - $"ORDER BY e.eventStreamOffset DESC"; - - var parameters = new SqlParameterCollection - { - new SqlParameter("@name", streamName) - }; - - return new SqlQuerySpec(query, parameters); - } - - public static SqlQuerySpec ByStreamName(string streamName, long streamPosition = 0) - { - var query = - $"SELECT * " + - $"FROM {Constants.Collection} e " + - $"WHERE " + - $" e.eventStream = @name " + - $"AND e.eventStreamOffset >= @position " + - $"ORDER BY e.eventStreamOffset ASC"; - - var parameters = new SqlParameterCollection - { - new SqlParameter("@name", streamName), - new SqlParameter("@position", streamPosition) - }; - - return new SqlQuerySpec(query, parameters); - } - - public static SqlQuerySpec ByStreamNameDesc(string streamName, long take) - { - var query = - $"SELECT TOP {take}* " + - $"FROM {Constants.Collection} e " + - $"WHERE " + - $" e.eventStream = @name " + - $"ORDER BY e.eventStreamOffset DESC"; - - var parameters = new SqlParameterCollection - { - new SqlParameter("@name", streamName) - }; - - return new SqlQuerySpec(query, parameters); - } - - public static SqlQuerySpec CreateByFilter(string? streamFilter, StreamPosition streamPosition, string sortOrder, long take) - { - var filters = new List(); - - var parameters = new SqlParameterCollection(); - - filters.ForPosition(parameters, streamPosition); - filters.ForRegex(parameters, streamFilter); - - return BuildQuery(filters, parameters, sortOrder, take); - } - - private static SqlQuerySpec BuildQuery(IEnumerable filters, SqlParameterCollection parameters, string sortOrder, long take) - { - var query = $"SELECT TOP {take} * FROM {Constants.Collection} e WHERE {string.Join(" AND ", filters)} ORDER BY e.timestamp {sortOrder}"; - - return new SqlQuerySpec(query, parameters); - } - - private static void ForRegex(this ICollection filters, SqlParameterCollection parameters, string? streamFilter) - { - if (!StreamFilter.IsAll(streamFilter)) - { - if (streamFilter.Contains("^")) - { - filters.Add("STARTSWITH(e.eventStream, @filter)"); - } - else - { - filters.Add("e.eventStream = @filter"); - } - - parameters.Add(new SqlParameter("@filter", streamFilter)); - } - } - - private static void ForPosition(this ICollection filters, SqlParameterCollection parameters, StreamPosition streamPosition) - { - if (streamPosition.IsEndOfCommit) - { - filters.Add("e.timestamp > @time"); - } - else - { - filters.Add("e.timestamp >= @time"); - } - - parameters.Add(new SqlParameter("@time", streamPosition.Timestamp)); - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterExtensions.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterExtensions.cs deleted file mode 100644 index 012baf0b6b..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/FilterExtensions.cs +++ /dev/null @@ -1,112 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.Documents; -using Microsoft.Azure.Documents.Client; -using Microsoft.Azure.Documents.Linq; - -namespace Squidex.Infrastructure.EventSourcing -{ - internal static class FilterExtensions - { - private static readonly FeedOptions CrossPartition = new FeedOptions - { - EnableCrossPartitionQuery = true - }; - - public static async Task FirstOrDefaultAsync(this IQueryable queryable, - CancellationToken ct = default) - { - var documentQuery = queryable.AsDocumentQuery(); - - using (documentQuery) - { - if (documentQuery.HasMoreResults) - { - var results = await documentQuery.ExecuteNextAsync(ct); - - return results.FirstOrDefault(); - } - } - - return default!; - } - - public static async IAsyncEnumerable QueryAsync(this DocumentClient documentClient, Uri collectionUri, SqlQuerySpec querySpec, - [EnumeratorCancellation] CancellationToken ct = default) - { - var query = documentClient.CreateDocumentQuery(collectionUri, querySpec, CrossPartition); - - var documentQuery = query.AsDocumentQuery(); - - using (documentQuery) - { - while (documentQuery.HasMoreResults && !ct.IsCancellationRequested) - { - var items = await documentQuery.ExecuteNextAsync(ct); - - foreach (var item in items) - { - yield return item; - } - } - } - } - - public static IEnumerable Filtered(this CosmosDbEventCommit commit, StreamPosition lastPosition) - { - var eventStreamOffset = commit.EventStreamOffset; - - var commitTimestamp = commit.Timestamp; - var commitOffset = 0; - - foreach (var @event in commit.Events) - { - eventStreamOffset++; - - if (commitOffset > lastPosition.CommitOffset || commitTimestamp > lastPosition.Timestamp) - { - var eventData = @event.ToEventData(); - var eventPosition = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length); - - yield return new StoredEvent(commit.EventStream, eventPosition, eventStreamOffset, eventData); - } - - commitOffset++; - } - } - - public static IEnumerable Filtered(this CosmosDbEventCommit commit, long streamPosition = EtagVersion.Empty) - { - var eventStreamOffset = commit.EventStreamOffset; - - var commitTimestamp = commit.Timestamp; - var commitOffset = 0; - - foreach (var @event in commit.Events) - { - eventStreamOffset++; - - if (eventStreamOffset >= streamPosition) - { - var eventData = @event.ToEventData(); - var eventPosition = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length); - - yield return new StoredEvent(commit.EventStream, eventPosition, eventStreamOffset, eventData); - } - - commitOffset++; - } - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/StreamPosition.cs b/backend/src/Squidex.Infrastructure.Azure/EventSourcing/StreamPosition.cs deleted file mode 100644 index b335f0153c..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/EventSourcing/StreamPosition.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.ObjectPool; - -namespace Squidex.Infrastructure.EventSourcing -{ - internal sealed class StreamPosition - { - public static readonly StreamPosition Empty = new StreamPosition(0, -1, -1); - - public long Timestamp { get; } - - public long CommitOffset { get; } - - public long CommitSize { get; } - - public bool IsEndOfCommit - { - get => CommitOffset == CommitSize - 1; - } - - public StreamPosition(long timestamp, long commitOffset, long commitSize) - { - Timestamp = timestamp; - - CommitOffset = commitOffset; - CommitSize = commitSize; - } - - public static implicit operator string(StreamPosition position) - { - var sb = DefaultPools.StringBuilder.Get(); - try - { - sb.Append(position.Timestamp); - sb.Append('-'); - sb.Append(position.CommitOffset); - sb.Append('-'); - sb.Append(position.CommitSize); - - return sb.ToString(); - } - finally - { - DefaultPools.StringBuilder.Return(sb); - } - } - - public static implicit operator StreamPosition(string? position) - { - if (!string.IsNullOrWhiteSpace(position)) - { - var parts = position.Split('-'); - - if (parts.Length == 3) - { - return new StreamPosition( - long.Parse(parts[0]), - long.Parse(parts[1]), - long.Parse(parts[2])); - } - } - - return Empty; - } - } -} diff --git a/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj b/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj deleted file mode 100644 index baed81dd81..0000000000 --- a/backend/src/Squidex.Infrastructure.Azure/Squidex.Infrastructure.Azure.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - net8.0 - Squidex.Infrastructure - latest - enable - enable - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs b/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs deleted file mode 100644 index 612d91c3c0..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs +++ /dev/null @@ -1,25 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using EventStore.Client; -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace Squidex.Infrastructure.Diagnostics; - -public sealed class GetEventStoreHealthCheck(EventStoreClientSettings settings) : IHealthCheck -{ - private readonly EventStoreClient client = new EventStoreClient(settings); - - public async Task CheckHealthAsync(HealthCheckContext context, - CancellationToken cancellationToken = default) - { - await client.ReadStreamAsync(Direction.Forwards, "test", default, cancellationToken: cancellationToken) - .FirstOrDefaultAsync(cancellationToken); - - return HealthCheckResult.Healthy("Application must query data from EventStore."); - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/EventStoreProjectionClient.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/EventStoreProjectionClient.cs deleted file mode 100644 index 7a0fc3a96d..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/EventStoreProjectionClient.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Concurrent; -using EventStore.Client; -using Squidex.Text; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class EventStoreProjectionClient(EventStoreClientSettings settings, string projectionPrefix) -{ - private readonly ConcurrentDictionary projections = new ConcurrentDictionary(); - private readonly EventStoreProjectionManagementClient client = new EventStoreProjectionManagementClient(settings); - - private string CreateFilterProjectionName(string filter) - { - return $"by-{projectionPrefix.Slugify()}-{filter.Slugify()}"; - } - - public async Task CreateProjectionAsync(StreamFilter filter) - { - if (filter.Kind == StreamFilterKind.MatchFull && filter.Prefixes?.Count == 1) - { - return $"{projectionPrefix}-{filter.Prefixes[0]}"; - } - - var regex = filter.ToRegex(); - - var name = CreateFilterProjectionName(regex); - - var query = - $@"fromAll() - .when({{ - $any: function (s, e) {{ - if (e.streamId.indexOf('{projectionPrefix}') === 0 && /{regex}/.test(e.streamId.substring({projectionPrefix.Length + 1}))) {{ - linkTo('{name}', e); - }} - }} - }});"; - - await CreateProjectionAsync(name, query); - - return name; - } - - private async Task CreateProjectionAsync(string name, string query) - { - if (projections.TryAdd(name, true)) - { - try - { - await client.CreateContinuousAsync(name, "fromAll().when()"); - await client.UpdateAsync(name, query, true); - } - catch (Exception ex) - { - if (!ex.Is()) - { - throw; - } - } - } - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs deleted file mode 100644 index 7d3971c551..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs +++ /dev/null @@ -1,81 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; -using System.Text; -using EventStore.Client; -using Squidex.Infrastructure.Json; -using EventStoreData = EventStore.Client.EventData; - -namespace Squidex.Infrastructure.EventSourcing; - -public static class Formatter -{ - private static readonly HashSet PrivateHeaders = ["$v", "$p", "$c", "$causedBy"]; - - public static StoredEvent Read(ResolvedEvent resolvedEvent, string? prefix, IJsonSerializer serializer) - { - var @event = resolvedEvent.Event; - - var eventPayload = Encoding.UTF8.GetString(@event.Data.Span); - var eventHeaders = GetHeaders(serializer, @event); - - var eventData = new EventData(@event.EventType, eventHeaders, eventPayload); - - var streamName = GetStreamName(prefix, @event); - - return new StoredEvent( - streamName, - resolvedEvent.OriginalEventNumber.ToInt64().ToString(CultureInfo.InvariantCulture), - resolvedEvent.Event.EventNumber.ToInt64(), - eventData); - } - - private static string GetStreamName(string? prefix, EventRecord @event) - { - var streamName = @event.EventStreamId; - - if (prefix != null && streamName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - { - streamName = streamName[(prefix.Length + 1)..]; - } - - return streamName; - } - - private static EnvelopeHeaders GetHeaders(IJsonSerializer serializer, EventRecord @event) - { - var headers = Deserialize(serializer, @event.Metadata); - - foreach (var key in headers.Keys.ToList()) - { - if (PrivateHeaders.Contains(key)) - { - headers.Remove(key); - } - } - - return headers; - } - - private static T Deserialize(IJsonSerializer serializer, ReadOnlyMemory source) - { - var json = Encoding.UTF8.GetString(source.Span); - - return serializer.Deserialize(json); - } - - public static EventStoreData Write(EventData eventData, IJsonSerializer serializer) - { - var payload = Encoding.UTF8.GetBytes(eventData.Payload); - - var headersJson = serializer.SerializeToBytes(eventData.Headers); - var headersBytes = headersJson; - - return new EventStoreData(Uuid.FromGuid(Guid.NewGuid()), eventData.Type, payload, headersBytes); - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs deleted file mode 100644 index 6817e8e8c6..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs +++ /dev/null @@ -1,204 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Runtime.CompilerServices; -using EventStore.Client; -using NodaTime; -using Squidex.Hosting; -using Squidex.Hosting.Configuration; -using Squidex.Infrastructure.Json; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class GetEventStore(EventStoreClientSettings settings, IJsonSerializer serializer) : IEventStore, IInitializable -{ - private const string StreamPrefix = "squidex"; - private readonly EventStoreClient client = new EventStoreClient(settings); - private readonly EventStoreProjectionClient projectionClient = new EventStoreProjectionClient(settings, StreamPrefix); - - public async Task InitializeAsync( - CancellationToken ct) - { - try - { - await client.DeleteAsync(Guid.NewGuid().ToString(), StreamState.Any, cancellationToken: ct); - } - catch (Exception ex) - { - var error = new ConfigurationError("GetEventStore cannot connect to event store."); - - throw new ConfigurationException(error, ex); - } - } - - public IEventSubscription CreateSubscription(IEventSubscriber subscriber, StreamFilter filter, string? position = null) - { - Guard.NotNull(filter); - - return new GetEventStoreSubscription(subscriber, client, projectionClient, serializer, position, StreamPrefix, filter); - } - - public async IAsyncEnumerable QueryAllAsync(StreamFilter filter, string? position = null, int take = int.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - if (take <= 0) - { - yield break; - } - - var streamName = await projectionClient.CreateProjectionAsync(filter); - - var stream = QueryAsync(streamName, position.ToPosition(false), take, ct); - - await foreach (var storedEvent in stream.IgnoreNotFound(ct)) - { - yield return storedEvent; - } - } - - public async IAsyncEnumerable QueryAllReverseAsync(StreamFilter filter, Instant timestamp = default, int take = int.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - if (take <= 0) - { - yield break; - } - - var streamName = await projectionClient.CreateProjectionAsync(filter); - - var stream = QueryReverseAsync(streamName, StreamPosition.End, take, ct); - - await foreach (var storedEvent in stream.IgnoreNotFound(ct).TakeWhile(x => x.Data.Headers.Timestamp() >= timestamp).WithCancellation(ct)) - { - yield return storedEvent; - } - } - - public async Task> QueryStreamAsync(string streamName, long afterStreamPosition = EtagVersion.Empty, - CancellationToken ct = default) - { - using (Telemetry.Activities.StartActivity("GetEventStore/QueryAsync")) - { - var result = new List(); - - var stream = QueryAsync(streamName, afterStreamPosition.ToPositionBefore(), int.MaxValue, ct); - - await foreach (var storedEvent in stream.IgnoreNotFound(ct)) - { - result.Add(storedEvent); - } - - return result.ToList(); - } - } - - private IAsyncEnumerable QueryAsync(string streamName, StreamPosition start, long count, - CancellationToken ct = default) - { - var result = client.ReadStreamAsync( - Direction.Forwards, - streamName, - start, - count, - true, - cancellationToken: ct); - - return result.Select(x => Formatter.Read(x, StreamPrefix, serializer)); - } - - private IAsyncEnumerable QueryReverseAsync(string streamName, StreamPosition start, long count, - CancellationToken ct = default) - { - var result = client.ReadStreamAsync( - Direction.Backwards, - streamName, - start, - count, - true, - cancellationToken: ct); - - return result.Select(x => Formatter.Read(x, StreamPrefix, serializer)); - } - - public async Task DeleteStreamAsync(string streamName, - CancellationToken ct = default) - { - Guard.NotNullOrEmpty(streamName); - - await client.DeleteAsync(GetStreamName(streamName), StreamState.Any, cancellationToken: ct); - } - - public async Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection events, - CancellationToken ct = default) - { - Guard.NotNullOrEmpty(streamName); - Guard.NotNull(events); - Guard.GreaterEquals(expectedVersion, EtagVersion.Any); - - using (Telemetry.Activities.StartActivity("GetEventStore/AppendEventsInternalAsync")) - { - if (events.Count == 0) - { - return; - } - - try - { - var eventData = events.Select(x => Formatter.Write(x, serializer)); - - streamName = GetStreamName(streamName); - - if (expectedVersion == -1) - { - await client.AppendToStreamAsync(streamName, StreamState.NoStream, eventData, cancellationToken: ct); - } - else if (expectedVersion < -1) - { - await client.AppendToStreamAsync(streamName, StreamState.Any, eventData, cancellationToken: ct); - } - else - { - await client.AppendToStreamAsync(streamName, expectedVersion.ToRevision(), eventData, cancellationToken: ct); - } - } - catch (WrongExpectedVersionException ex) - { - throw new WrongEventVersionException(ex.ActualVersion ?? 0, expectedVersion); - } - } - } - - public async Task DeleteAsync(StreamFilter filter, - CancellationToken ct = default) - { - var streamName = await projectionClient.CreateProjectionAsync(filter); - - var events = client.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start, resolveLinkTos: true, cancellationToken: ct); - - if (await events.ReadState == ReadState.StreamNotFound) - { - return; - } - - var deleted = new HashSet(); - - await foreach (var storedEvent in TaskAsyncEnumerableExtensions.WithCancellation(events, ct)) - { - var streamToDelete = storedEvent.Event.EventStreamId; - - if (deleted.Add(streamToDelete)) - { - await client.DeleteAsync(streamToDelete, StreamState.Any, cancellationToken: ct); - } - } - } - - private static string GetStreamName(string streamName) - { - return $"{StreamPrefix}-{streamName}"; - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs deleted file mode 100644 index cc9c7d802a..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs +++ /dev/null @@ -1,75 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using EventStore.Client; -using Squidex.Infrastructure.Json; - -namespace Squidex.Infrastructure.EventSourcing; - -internal sealed class GetEventStoreSubscription : IEventSubscription -{ - private readonly CancellationTokenSource cts = new CancellationTokenSource(); - - public GetEventStoreSubscription( - IEventSubscriber eventSubscriber, - EventStoreClient client, - EventStoreProjectionClient projectionClient, - IJsonSerializer serializer, - string? position, - string? prefix, - StreamFilter filter) - { - var ct = cts.Token; - -#pragma warning disable MA0134 // Observe result of async calls - Task.Run(async () => - { - var streamName = await projectionClient.CreateProjectionAsync(filter); - - var start = FromStream.Start; - if (!string.IsNullOrWhiteSpace(position)) - { - start = FromStream.After(position.ToPosition(true)); - } - - await using var subscription = client.SubscribeToStream(streamName, start, true, cancellationToken: ct); - try - { - await foreach (var message in subscription.Messages) - { - if (message is StreamMessage.Event @event) - { - var storedEvent = Formatter.Read(@event.ResolvedEvent, prefix, serializer); - - await eventSubscriber.OnNextAsync(this, storedEvent); - } - } - } - catch (Exception ex) - { - var inner = new InvalidOperationException($"Subscription closed.", ex); - - await eventSubscriber.OnErrorAsync(this, ex); - } - }, ct); -#pragma warning restore MA0134 // Observe result of async calls - } - - public void Dispose() - { - cts.Cancel(); - } - - public ValueTask CompleteAsync() - { - return default; - } - - public void WakeUp() - { - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Utils.cs b/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Utils.cs deleted file mode 100644 index 9284cf20ba..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Utils.cs +++ /dev/null @@ -1,97 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; -using System.Runtime.CompilerServices; -using EventStore.Client; - -namespace Squidex.Infrastructure.EventSourcing; - -public static class Utils -{ - public static StreamRevision ToRevision(this long version) - { - return StreamRevision.FromInt64(version); - } - - public static StreamPosition ToPositionBefore(this long version) - { - if (version < 0) - { - return StreamPosition.Start; - } - - return StreamPosition.FromInt64(version - 1); - } - - public static StreamPosition ToPosition(this string? position, bool inclusive) - { - if (string.IsNullOrWhiteSpace(position)) - { - return StreamPosition.Start; - } - - if (long.TryParse(position, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedPosition)) - { - if (!inclusive) - { - parsedPosition++; - } - - return StreamPosition.FromInt64(parsedPosition); - } - - return StreamPosition.Start; - } - - public static async IAsyncEnumerable IgnoreNotFound(this IAsyncEnumerable source, - [EnumeratorCancellation] CancellationToken ct = default) - { - var enumerator = source.GetAsyncEnumerator(ct); - - bool resultFound; - try - { - resultFound = await enumerator.MoveNextAsync(ct); - } - catch (StreamNotFoundException) - { - resultFound = false; - } - - if (!resultFound) - { - yield break; - } - - yield return enumerator.Current; - - while (await enumerator.MoveNextAsync(ct)) - { - ct.ThrowIfCancellationRequested(); - - yield return enumerator.Current; - } - } - - public static string ToRegex(this StreamFilter filter) - { - if (filter.Prefixes == null) - { - return ".*"; - } - - if (filter.Kind == StreamFilterKind.MatchStart) - { - return $"^{string.Join('|', filter.Prefixes.Select(p => $"({p})"))}"; - } - else - { - return $"^{string.Join('|', filter.Prefixes.Select(p => $"({p})"))}$"; - } - } -} diff --git a/backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj b/backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj deleted file mode 100644 index 70c6085e0f..0000000000 --- a/backend/src/Squidex.Infrastructure.GetEventStore/Squidex.Infrastructure.GetEventStore.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - net8.0 - Squidex.Infrastructure - latest - enable - enable - - - full - True - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/FilterExtensions.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/FilterExtensions.cs deleted file mode 100644 index fe75f1ff77..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/FilterExtensions.cs +++ /dev/null @@ -1,120 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Text.RegularExpressions; -using MongoDB.Driver; - -namespace Squidex.Infrastructure.EventSourcing; - -internal static class FilterExtensions -{ - public static FilterDefinition ByOffset(long streamPosition) - { - var builder = Builders.Filter; - - return builder.Gte(x => x.EventStreamOffset, streamPosition); - } - - public static FilterDefinition ByPosition(StreamPosition streamPosition) - { - var builder = Builders.Filter; - - if (streamPosition.IsEndOfCommit) - { - return builder.Gt(x => x.Timestamp, streamPosition.Timestamp); - } - else - { - return builder.Gte(x => x.Timestamp, streamPosition.Timestamp); - } - } - - public static FilterDefinition ByStream(StreamFilter filter) - { - var builder = Builders.Filter; - - if (filter.Prefixes == null) - { - return builder.Exists(x => x.EventStream, true); - } - - if (filter.Kind == StreamFilterKind.MatchStart) - { - return builder.Or(filter.Prefixes.Select(p => builder.Regex(x => x.EventStream, $"^{p}"))); - } - - return builder.In(x => x.EventStream, filter.Prefixes); - } - - public static FilterDefinition>? ByChangeInStream(StreamFilter filter) - { - var builder = Builders>.Filter; - - if (filter.Prefixes == null) - { - return null; - } - - if (filter.Kind == StreamFilterKind.MatchStart) - { - return builder.Or(filter.Prefixes.Select(p => builder.Regex(x => x.FullDocument.EventStream, $"^{Regex.Escape(p)}"))); - } - - return builder.In(x => x.FullDocument.EventStream, filter.Prefixes); - } - - public static IEnumerable Filtered(this MongoEventCommit commit, StreamPosition position) - { - var eventStreamOffset = commit.EventStreamOffset; - - var commitTimestamp = commit.Timestamp; - var commitOffset = 0; - - foreach (var @event in commit.Events) - { - eventStreamOffset++; - - if (commitOffset > position.CommitOffset || commitTimestamp > position.Timestamp) - { - var eventData = @event.ToEventData(); - var eventPosition = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length); - - yield return new StoredEvent(commit.EventStream, eventPosition, eventStreamOffset, eventData); - } - - commitOffset++; - } - } - - public static IEnumerable Filtered(this MongoEventCommit commit) - { - return commit.Filtered(EtagVersion.Empty); - } - - public static IEnumerable Filtered(this MongoEventCommit commit, long position) - { - var eventStreamOffset = commit.EventStreamOffset; - - var commitTimestamp = commit.Timestamp; - var commitOffset = 0; - - foreach (var @event in commit.Events) - { - eventStreamOffset++; - - if (eventStreamOffset > position) - { - var eventData = @event.ToEventData(); - var eventPosition = new StreamPosition(commitTimestamp, commitOffset, commit.Events.Length); - - yield return new StoredEvent(commit.EventStream, eventPosition, eventStreamOffset, eventData); - } - - commitOffset++; - } - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs deleted file mode 100644 index e416330b76..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs +++ /dev/null @@ -1,35 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson.Serialization.Attributes; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class MongoEvent -{ - [BsonElement] - [BsonRequired] - public string Type { get; set; } - - [BsonRequired] - [BsonElement(nameof(Payload))] - public string Payload { get; set; } - - [BsonRequired] - [BsonElement("Metadata")] - public EnvelopeHeaders Headers { get; set; } - - public static MongoEvent FromEventData(EventData data) - { - return new MongoEvent { Type = data.Type, Headers = data.Headers, Payload = data.Payload }; - } - - public EventData ToEventData() - { - return new EventData(Type, Headers, Payload); - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventCommit.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventCommit.cs deleted file mode 100644 index 2fd580d426..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventCommit.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class MongoEventCommit -{ - [BsonId] - [BsonElement] - [BsonRepresentation(BsonType.String)] - public Guid Id { get; set; } - - [BsonRequired] - [BsonElement(nameof(Timestamp))] - public BsonTimestamp Timestamp { get; set; } - - [BsonRequired] - [BsonElement(nameof(Events))] - public MongoEvent[] Events { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventStreamOffset))] - public long EventStreamOffset { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventsCount))] - public long EventsCount { get; set; } - - [BsonRequired] - [BsonElement(nameof(EventStream))] - public string EventStream { get; set; } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs deleted file mode 100644 index dd33e0a0c3..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs +++ /dev/null @@ -1,67 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Driver; -using MongoDB.Driver.Core.Clusters; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Infrastructure.EventSourcing; - -public partial class MongoEventStore(IMongoDatabase database) : MongoRepositoryBase(database), IEventStore -{ - public IMongoCollection RawCollection - { - get => Database.GetCollection(CollectionName()); - } - - public IMongoCollection TypedCollection - { - get => Collection; - } - - public bool CanUseChangeStreams { get; private set; } - - protected override string CollectionName() - { - return "Events2"; - } - - protected override MongoCollectionSettings CollectionSettings() - { - return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority }; - } - - protected override async Task SetupCollectionAsync(IMongoCollection collection, - CancellationToken ct) - { - await collection.Indexes.CreateManyAsync( - [ - new CreateIndexModel( - Index - .Ascending(x => x.EventStream) - .Ascending(x => x.Timestamp)), - new CreateIndexModel( - Index - .Descending(x => x.Timestamp) - .Ascending(x => x.EventStream)), - new CreateIndexModel( - Index - .Ascending(x => x.EventStream) - .Descending(x => x.EventStreamOffset), - new CreateIndexOptions - { - Unique = true - }) - ], ct); - - var clusterVersion = await Database.GetMajorVersionAsync(ct); - var clusteredAsReplica = Database.Client.Cluster.Description.Type == ClusterType.ReplicaSet; - - CanUseChangeStreams = clusteredAsReplica && clusterVersion >= 4; - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs deleted file mode 100644 index 435800dde0..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStoreSubscription.cs +++ /dev/null @@ -1,167 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Driver; -using NodaTime; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class MongoEventStoreSubscription : IEventSubscription -{ - private readonly MongoEventStore eventStore; - private readonly IEventSubscriber eventSubscriber; - private readonly CancellationTokenSource stopToken = new CancellationTokenSource(); - - public MongoEventStoreSubscription(MongoEventStore eventStore, IEventSubscriber eventSubscriber, StreamFilter streamFilter, string? position) - { - this.eventStore = eventStore; - this.eventSubscriber = eventSubscriber; - - QueryAsync(streamFilter, position).Forget(); - } - - private async Task QueryAsync(StreamFilter streamFilter, string? position) - { - try - { - string? lastRawPosition = null; - - try - { - lastRawPosition = await QueryOldAsync(streamFilter, position); - } - catch (OperationCanceledException) - { - } - - if (!stopToken.IsCancellationRequested) - { - await QueryCurrentAsync(streamFilter, lastRawPosition); - } - } - catch (Exception ex) - { - await eventSubscriber.OnErrorAsync(this, ex); - } - } - - private async Task QueryCurrentAsync(StreamFilter streamFilter, StreamPosition lastPosition) - { - BsonDocument? resumeToken = null; - - var start = - lastPosition.Timestamp.Timestamp > 0 ? - lastPosition.Timestamp.Timestamp - 30 : - SystemClock.Instance.GetCurrentInstant().Minus(Duration.FromSeconds(30)).ToUnixTimeSeconds(); - - var changePipeline = Match(streamFilter); - var changeStart = new BsonTimestamp((int)start, 0); - - while (!stopToken.IsCancellationRequested) - { - var changeOptions = new ChangeStreamOptions(); - - if (resumeToken != null) - { - changeOptions.StartAfter = resumeToken; - } - else - { - changeOptions.StartAtOperationTime = changeStart; - } - - using (var cursor = eventStore.TypedCollection.Watch(changePipeline, changeOptions, stopToken.Token)) - { - var isRead = false; - - await cursor.ForEachAsync(async change => - { - if (change.OperationType == ChangeStreamOperationType.Insert) - { - foreach (var storedEvent in change.FullDocument.Filtered(lastPosition)) - { - await eventSubscriber.OnNextAsync(this, storedEvent); - } - } - - isRead = true; - }, stopToken.Token); - - resumeToken = cursor.GetResumeToken(); - - if (!isRead) - { - await Task.Delay(1000, stopToken.Token); - } - } - } - } - - private async Task QueryOldAsync(StreamFilter streamFilter, string? position) - { - string? lastRawPosition = null; - - using (var cts = new CancellationTokenSource()) - { - using (var combined = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, stopToken.Token)) - { - await foreach (var storedEvent in eventStore.QueryAllAsync(streamFilter, position, ct: combined.Token)) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - var timeToNow = now - storedEvent.Data.Headers.Timestamp(); - - if (timeToNow <= Duration.FromMinutes(5)) - { - await cts.CancelAsync(); - } - else - { - await eventSubscriber.OnNextAsync(this, storedEvent); - - lastRawPosition = storedEvent.EventPosition; - } - } - } - } - - return lastRawPosition; - } - - private static PipelineDefinition, ChangeStreamDocument>? Match(StreamFilter streamFilter) - { - var result = new EmptyPipelineDefinition>(); - - var byStream = FilterExtensions.ByChangeInStream(streamFilter); - - if (byStream != null) - { - var filterBuilder = Builders>.Filter; - var filterExpression = filterBuilder.Or(filterBuilder.Ne(x => x.OperationType, ChangeStreamOperationType.Insert), byStream); - - return result.Match(filterExpression); - } - - return result; - } - - public void Dispose() - { - stopToken.Cancel(); - } - - public ValueTask CompleteAsync() - { - return default; - } - - public void WakeUp() - { - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs deleted file mode 100644 index bb9986a6f4..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Reader.cs +++ /dev/null @@ -1,157 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Runtime.CompilerServices; -using MongoDB.Driver; -using NodaTime; -using Squidex.Infrastructure.MongoDb; - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Infrastructure.EventSourcing; - -public delegate bool EventPredicate(MongoEvent data); - -public partial class MongoEventStore : MongoRepositoryBase, IEventStore -{ - private static readonly List EmptyEvents = []; - - public IEventSubscription CreateSubscription(IEventSubscriber subscriber, StreamFilter filter, string? position = null) - { - Guard.NotNull(subscriber); - - if (CanUseChangeStreams) - { - return new MongoEventStoreSubscription(this, subscriber, filter, position); - } - else - { - return new PollingSubscription(this, subscriber, filter, position); - } - } - - public async Task> QueryStreamAsync(string streamName, long afterStreamPosition = EtagVersion.Empty, - CancellationToken ct = default) - { - using (Telemetry.Activities.StartActivity("MongoEventStore/QueryAsync")) - { - var commits = - await Collection.Find(CreateFilter(StreamFilter.Name(streamName), afterStreamPosition)) - .ToListAsync(ct); - - var result = Convert(commits, afterStreamPosition); - - if ((commits.Count == 0 || commits[0].EventStreamOffset != afterStreamPosition) && afterStreamPosition > EtagVersion.Empty) - { - var filterBefore = - Filter.And( - FilterExtensions.ByStream(StreamFilter.Name(streamName)), - Filter.Lt(x => x.EventStreamOffset, afterStreamPosition)); - - commits = - await Collection.Find(filterBefore).SortByDescending(x => x.EventStreamOffset).Limit(1) - .ToListAsync(ct); - - result = Convert(commits, afterStreamPosition).Concat(result).ToList(); - } - - return result; - } - } - - public async IAsyncEnumerable QueryAllReverseAsync(StreamFilter filter, Instant timestamp = default, int take = int.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - if (take <= 0) - { - yield break; - } - - StreamPosition lastPosition = timestamp; - - var find = - Collection.Find(CreateFilter(filter, lastPosition), Batching.Options) - .Limit(take).Sort(Sort.Descending(x => x.Timestamp).Ascending(x => x.EventStream)); - - var taken = 0; - - using (var cursor = await find.ToCursorAsync(ct)) - { - while (taken < take && await cursor.MoveNextAsync(ct)) - { - foreach (var current in cursor.Current) - { - foreach (var @event in current.Filtered(lastPosition).Reverse()) - { - yield return @event; - - taken++; - - if (taken == take) - { - break; - } - } - - if (taken == take) - { - break; - } - } - } - } - } - - public async IAsyncEnumerable QueryAllAsync(StreamFilter filter, string? position = null, int take = int.MaxValue, - [EnumeratorCancellation] CancellationToken ct = default) - { - StreamPosition lastPosition = position; - - var filterDefinition = CreateFilter(filter, lastPosition); - - var find = - Collection.Find(filterDefinition).SortBy(x => x.Timestamp).ThenByDescending(x => x.EventStream) - .Limit(take); - - var taken = 0; - - await foreach (var current in find.ToAsyncEnumerable(ct)) - { - foreach (var @event in current.Filtered(lastPosition)) - { - yield return @event; - - taken++; - - if (taken == take) - { - break; - } - } - } - } - - private static IReadOnlyList Convert(IEnumerable commits) - { - return commits.OrderBy(x => x.EventStreamOffset).ThenBy(x => x.Timestamp).SelectMany(x => x.Filtered()).ToList(); - } - - private static IReadOnlyList Convert(IEnumerable commits, long streamPosition) - { - return commits.OrderBy(x => x.EventStreamOffset).ThenBy(x => x.Timestamp).SelectMany(x => x.Filtered(streamPosition)).ToList(); - } - - private static FilterDefinition CreateFilter(StreamFilter filter, StreamPosition streamPosition) - { - return Filter.And(FilterExtensions.ByPosition(streamPosition), FilterExtensions.ByStream(filter)); - } - - private static FilterDefinition CreateFilter(StreamFilter filter, long streamPosition) - { - return Filter.And(FilterExtensions.ByStream(filter), FilterExtensions.ByOffset(streamPosition)); - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs deleted file mode 100644 index cfc17fe194..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore_Writer.cs +++ /dev/null @@ -1,151 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Driver; - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Infrastructure.EventSourcing; - -public partial class MongoEventStore -{ - private const int MaxWriteAttempts = 20; - private static readonly BsonTimestamp EmptyTimestamp = new BsonTimestamp(0); - - public Task DeleteAsync(StreamFilter filter, - CancellationToken ct = default) - { - Guard.NotDefault(filter); - - return Collection.DeleteManyAsync(FilterExtensions.ByStream(filter), ct); - } - - public async Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection events, - CancellationToken ct = default) - { - Guard.NotEmpty(commitId); - Guard.NotNullOrEmpty(streamName); - Guard.NotNull(events); - Guard.GreaterEquals(expectedVersion, EtagVersion.Any); - - using (Telemetry.Activities.StartActivity("MongoEventStore/AppendAsync")) - { - if (events.Count == 0) - { - return; - } - - var currentVersion = await GetEventStreamOffsetAsync(streamName, ct); - - if (expectedVersion > EtagVersion.Any && expectedVersion != currentVersion) - { - throw new WrongEventVersionException(currentVersion, expectedVersion); - } - - var commit = BuildCommit(commitId, streamName, expectedVersion >= -1 ? expectedVersion : currentVersion, events); - - for (var attempt = 1; attempt <= MaxWriteAttempts; attempt++) - { - try - { - await Collection.InsertOneAsync(commit, cancellationToken: ct); - return; - } - catch (MongoWriteException ex) - { - if (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) - { - currentVersion = await GetEventStreamOffsetAsync(streamName, ct); - - if (expectedVersion > EtagVersion.Any) - { - throw new WrongEventVersionException(currentVersion, expectedVersion); - } - - if (attempt >= MaxWriteAttempts) - { - throw new TimeoutException("Could not acquire a free slot for the commit within the provided time."); - } - } - else - { - throw; - } - } - } - } - } - - public async Task AppendUnsafeAsync(IEnumerable commits, - CancellationToken ct = default) - { - Guard.NotNull(commits); - - using (Telemetry.Activities.StartActivity("MongoEventStore/AppendUnsafeAsync")) - { - var writes = new List>(); - - foreach (var commit in commits) - { - var document = BuildCommit(commit.Id, commit.StreamName, commit.Offset, commit.Events); - - writes.Add(new InsertOneModel(document)); - } - - if (writes.Count > 0) - { - await Collection.BulkWriteAsync(writes, BulkUnordered, ct); - } - } - } - - private async Task GetEventStreamOffsetAsync(string streamName, - CancellationToken ct = default) - { - var document = - await Collection.Find(Filter.Eq(x => x.EventStream, streamName)) - .Project(Projection - .Include(x => x.EventStreamOffset) - .Include(x => x.EventsCount)) - .Sort(Sort.Descending(x => x.EventStreamOffset)).Limit(1) - .FirstOrDefaultAsync(ct); - - if (document != null) - { - return document[nameof(MongoEventCommit.EventStreamOffset)].ToInt64() + document[nameof(MongoEventCommit.EventsCount)].ToInt64(); - } - - return EtagVersion.Empty; - } - - private static MongoEventCommit BuildCommit(Guid commitId, string streamName, long expectedVersion, ICollection events) - { - var commitEvents = new MongoEvent[events.Count]; - - var i = 0; - - foreach (var e in events) - { - var mongoEvent = MongoEvent.FromEventData(e); - - commitEvents[i++] = mongoEvent; - } - - var mongoCommit = new MongoEventCommit - { - Id = commitId, - Events = commitEvents, - EventsCount = events.Count, - EventStream = streamName, - EventStreamOffset = expectedVersion, - Timestamp = EmptyTimestamp - }; - - return mongoCommit; - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/StreamPosition.cs b/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/StreamPosition.cs deleted file mode 100644 index 230cb71f12..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/EventSourcing/StreamPosition.cs +++ /dev/null @@ -1,79 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; -using MongoDB.Bson; -using NodaTime; -using Squidex.Infrastructure.ObjectPool; - -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter -#pragma warning disable RECS0082 // Parameter has the same name as a member and hides it - -namespace Squidex.Infrastructure.EventSourcing; - -internal sealed record StreamPosition(BsonTimestamp Timestamp, long CommitOffset, long CommitSize) -{ - public static readonly StreamPosition Empty = new StreamPosition(new BsonTimestamp(0, 0), -1, -1); - - public bool IsEndOfCommit => CommitOffset == CommitSize - 1; - - public static implicit operator string(StreamPosition position) - { - var sb = DefaultPools.StringBuilder.Get(); - try - { - sb.Append(position.Timestamp.Timestamp); - sb.Append('-'); - sb.Append(position.Timestamp.Increment); - sb.Append('-'); - sb.Append(position.CommitOffset); - sb.Append('-'); - sb.Append(position.CommitSize); - - return sb.ToString(); - } - finally - { - DefaultPools.StringBuilder.Return(sb); - } - } - - public static implicit operator StreamPosition(string? position) - { - if (!string.IsNullOrWhiteSpace(position)) - { - var parts = position.Split('-'); - - if (parts.Length == 4) - { - var culture = CultureInfo.InvariantCulture; - - return new StreamPosition( - new BsonTimestamp( - int.Parse(parts[0], NumberStyles.Integer, culture), - int.Parse(parts[1], NumberStyles.Integer, culture)), - long.Parse(parts[2], NumberStyles.Integer, culture), - long.Parse(parts[3], NumberStyles.Integer, culture)); - } - } - - return Empty; - } - - public static implicit operator StreamPosition(Instant timestamp) - { - if (timestamp != default) - { - return new StreamPosition( - new BsonTimestamp((int)timestamp.ToUnixTimeSeconds(), 0), - 0, - 0); - } - - return Empty; - } -} diff --git a/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj b/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj deleted file mode 100644 index 57ae2e7ec9..0000000000 --- a/backend/src/Squidex.Infrastructure.MongoDb/Squidex.Infrastructure.MongoDb.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - net8.0 - Squidex.Infrastructure - latest - enable - enable - - - full - True - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - diff --git a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs index 94ae6ba1b1..93dbb7657d 100644 --- a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Squidex.Caching; +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/BatchSubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/BatchSubscription.cs index 1b89f757c4..7872f32841 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/BatchSubscription.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/BatchSubscription.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Channels; +using Squidex.Events; using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.EventSourcing.Consume; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs index 582b9f3d30..e53dfcfe40 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/EventConsumerProcessor.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; +using Squidex.Events; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParseSubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParseSubscription.cs index 64c33bcf15..856bd6fdeb 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParseSubscription.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParseSubscription.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Threading.Channels; +using Squidex.Events; namespace Squidex.Infrastructure.EventSourcing.Consume; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParsedEvent.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParsedEvent.cs index 13b245afc2..d9ff4bc419 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParsedEvent.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Consume/ParsedEvent.cs @@ -8,8 +8,10 @@ #pragma warning disable SA1313 // Parameter names should begin with lower-case letter #pragma warning disable MA0048 // File name must match type name +using Squidex.Events; + namespace Squidex.Infrastructure.EventSourcing.Consume; -public record struct ParsedEvent(Envelope? Event, string Position); +public record struct ParsedEvent(Envelope? Event, StreamPosition Position); -public record struct ParsedEvents(List> Events, string Position); +public record struct ParsedEvents(List> Events, StreamPosition Position); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs b/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs index f9adc9a3b0..fcbf26bab4 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/DefaultEventFormatter.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Reflection; @@ -49,7 +50,7 @@ public Envelope Parse(StoredEvent storedEvent) var envelope = new Envelope(payload, storedEvent.Data.Headers); - envelope.SetEventPosition(storedEvent.EventPosition); + envelope.SetEventPosition(storedEvent.EventPosition!); envelope.SetEventStreamNumber(storedEvent.EventStreamNumber); return envelope; diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs index 0e27d82c7c..a6d12ed64d 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Globalization; using NodaTime; using NodaTime.Text; +using Squidex.Events; namespace Squidex.Infrastructure.EventSourcing; @@ -56,7 +56,7 @@ public static DomainId AggregateId(this EnvelopeHeaders headers) public static Envelope SetAggregateId(this Envelope envelope, DomainId value) where T : class, IEvent { - envelope.Headers[CommonHeaders.AggregateId] = value; + envelope.Headers[CommonHeaders.AggregateId] = value.ToString(); return envelope; } @@ -73,14 +73,14 @@ public static Envelope SetEventId(this Envelope envelope, Guid value) w return envelope; } - public static Instant Timestamp(this EnvelopeHeaders headers) + public static Instant TimestampAsInstant(this EnvelopeHeaders headers) { return headers.GetInstant(CommonHeaders.Timestamp); } public static Envelope SetTimestamp(this Envelope envelope, Instant value) where T : class, IEvent { - envelope.Headers[CommonHeaders.Timestamp] = value; + envelope.Headers[CommonHeaders.Timestamp] = value.ToString(); return envelope; } @@ -97,26 +97,11 @@ public static Envelope SetRestored(this Envelope envelope, bool value = return envelope; } - public static long GetLong(this EnvelopeHeaders obj, string key) - { - if (obj.TryGetValue(key, out var found)) - { - if (found.Value is double d) - { - return (long)d; - } - else if (found.Value is string s && double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) - { - return (long)result; - } - } - - return 0; - } - public static Guid GetGuid(this EnvelopeHeaders obj, string key) { - if (obj.TryGetValue(key, out var found) && found.Value is string s && Guid.TryParse(s, out var guid)) + var s = obj.GetString(key); + + if (Guid.TryParse(s, out var guid)) { return guid; } @@ -126,31 +111,13 @@ public static Guid GetGuid(this EnvelopeHeaders obj, string key) public static Instant GetInstant(this EnvelopeHeaders obj, string key) { - if (obj.TryGetValue(key, out var found) && found.Value is string s && InstantPattern.ExtendedIso.Parse(s).TryGetValue(default, out var instant)) + var s = obj.GetString(key); + + if (InstantPattern.ExtendedIso.Parse(s).TryGetValue(default, out var instant)) { return instant; } return default; } - - public static string GetString(this EnvelopeHeaders obj, string key) - { - if (obj.TryGetValue(key, out var found)) - { - return found.ToString(); - } - - return string.Empty; - } - - public static bool GetBoolean(this EnvelopeHeaders obj, string key) - { - if (obj.TryGetValue(key, out var found) && found.Value is bool b) - { - return b; - } - - return false; - } } diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs deleted file mode 100644 index f951167f52..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.Json.Objects; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class EnvelopeHeaders : Dictionary -{ - public EnvelopeHeaders() - { - } - - public EnvelopeHeaders(IDictionary headers) - : base(headers) - { - } - - public EnvelopeHeaders CloneHeaders() - { - return new EnvelopeHeaders(this); - } -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/Envelope{T}.cs b/backend/src/Squidex.Infrastructure/EventSourcing/Envelope{T}.cs index 80b0974292..c5ef40a14d 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/Envelope{T}.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/Envelope{T}.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; + namespace Squidex.Infrastructure.EventSourcing; public sealed class Envelope(T payload, EnvelopeHeaders? headers = null) where T : class, IEvent diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EventCommit.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EventCommitBuilder.cs similarity index 74% rename from backend/src/Squidex.Infrastructure/EventSourcing/EventCommit.cs rename to backend/src/Squidex.Infrastructure/EventSourcing/EventCommitBuilder.cs index da72eb5bbe..86fb876d99 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EventCommit.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/EventCommitBuilder.cs @@ -7,15 +7,12 @@ #pragma warning disable SA1313 // Parameter names should begin with lower-case letter +using Squidex.Events; + namespace Squidex.Infrastructure.EventSourcing; -public sealed record EventCommit(Guid Id, string StreamName, long Offset, ICollection Events) +public static class EventCommitBuilder { - public static EventCommit Create(Guid id, string streamName, long offset, EventData @event) - { - return new EventCommit(id, streamName, offset, [@event]); - } - public static EventCommit Create(string streamName, long offset, Envelope envelope, IEventFormatter eventFormatter) { var id = Guid.NewGuid(); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/EventData.cs b/backend/src/Squidex.Infrastructure/EventSourcing/EventData.cs deleted file mode 100644 index 6f2a841aba..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/EventData.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed record EventData(string Type, EnvelopeHeaders Headers, string Payload); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs b/backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs index f3a412627d..5f7fad1d8f 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/IEventConsumer.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; + namespace Squidex.Infrastructure.EventSourcing; public interface IEventConsumer diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs b/backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs index e9ab3cb214..6985e5be94 100644 --- a/backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs +++ b/backend/src/Squidex.Infrastructure/EventSourcing/IEventFormatter.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; + namespace Squidex.Infrastructure.EventSourcing; public interface IEventFormatter diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/IEventStore.cs b/backend/src/Squidex.Infrastructure/EventSourcing/IEventStore.cs deleted file mode 100644 index 8f63ee0165..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/IEventStore.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using NodaTime; - -namespace Squidex.Infrastructure.EventSourcing; - -public interface IEventStore -{ - Task> QueryStreamAsync(string streamName, long afterStreamPosition = EtagVersion.Empty, - CancellationToken ct = default); - - IAsyncEnumerable QueryAllReverseAsync(StreamFilter filter, Instant timestamp = default, int take = int.MaxValue, - CancellationToken ct = default); - - IAsyncEnumerable QueryAllAsync(StreamFilter filter, string? position = null, int take = int.MaxValue, - CancellationToken ct = default); - - Task AppendAsync(Guid commitId, string streamName, long expectedVersion, ICollection events, - CancellationToken ct = default); - - Task DeleteAsync(StreamFilter filter, - CancellationToken ct = default); - - IEventSubscription CreateSubscription(IEventSubscriber eventSubscriber, StreamFilter filter, string? position = null); - - async Task AppendUnsafeAsync(IEnumerable commits, - CancellationToken ct = default) - { - foreach (var commit in commits) - { - await AppendAsync(commit.Id, commit.StreamName, commit.Offset, commit.Events, ct); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscriber.cs b/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscriber.cs deleted file mode 100644 index 267d1ae7e4..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscriber.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable MA0048 // File name must match type name - -namespace Squidex.Infrastructure.EventSourcing; - -public delegate IEventSubscription EventSubscriptionSource(IEventSubscriber target); - -public interface IEventSubscriber -{ - ValueTask OnNextAsync(IEventSubscription subscription, T @event); - - ValueTask OnErrorAsync(IEventSubscription subscription, Exception exception); -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs deleted file mode 100644 index 8dd024b60c..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/IEventSubscription.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -public interface IEventSubscription : IDisposable -{ - void WakeUp(); - - ValueTask CompleteAsync(); -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs deleted file mode 100644 index e6129c5f5f..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/PollingSubscription.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.Tasks; -using Squidex.Infrastructure.Timers; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class PollingSubscription : IEventSubscription -{ - private readonly CompletionTimer timer; - - public PollingSubscription( - IEventStore eventStore, - IEventSubscriber eventSubscriber, - StreamFilter streamFilter, - string? position) - { - timer = new CompletionTimer(5000, async ct => - { - try - { - await foreach (var storedEvent in eventStore.QueryAllAsync(streamFilter, position, ct: ct)) - { - await eventSubscriber.OnNextAsync(this, storedEvent); - - position = storedEvent.EventPosition; - } - } - catch (Exception ex) - { - await eventSubscriber.OnErrorAsync(this, ex); - } - }); - } - - public ValueTask CompleteAsync() - { - return new ValueTask(timer.StopAsync()); - } - - public void Dispose() - { - timer.StopAsync().Forget(); - } - - public void WakeUp() - { - timer.SkipCurrentDelay(); - } -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs b/backend/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs deleted file mode 100644 index 1dec8ad638..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/RetrySubscription.cs +++ /dev/null @@ -1,131 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class RetrySubscription : IEventSubscription, IEventSubscriber -{ - private readonly RetryWindow retryWindow = new RetryWindow(TimeSpan.FromMinutes(5), 5); - private readonly IEventSubscriber eventSubscriber; - private readonly EventSubscriptionSource eventSource; - private SubscriptionHolder? currentSubscription; - - public int ReconnectWaitMs { get; set; } = 5000; - - public bool IsSubscribed => currentSubscription != null; - - // Holds all information for a current subscription. Therefore we only have to maintain one reference. - private sealed class SubscriptionHolder(IEventSubscription subscription) : IDisposable - { - public CancellationTokenSource Cancellation { get; } = new CancellationTokenSource(); - - public IEventSubscription Subscription { get; } = subscription; - - public void Dispose() - { - Cancellation.Cancel(); - - Subscription.Dispose(); - } - } - - public RetrySubscription(IEventSubscriber eventSubscriber, - EventSubscriptionSource eventSource) - { - Guard.NotNull(eventSubscriber); - Guard.NotNull(eventSource); - - this.eventSubscriber = eventSubscriber; - this.eventSource = eventSource; - - Subscribe(); - } - - public void Dispose() - { - Unsubscribe(); - } - - private void Subscribe() - { - lock (retryWindow) - { - if (currentSubscription != null) - { - return; - } - - currentSubscription = new SubscriptionHolder(eventSource(this)); - } - } - - private void Unsubscribe() - { - lock (retryWindow) - { - if (currentSubscription == null) - { - return; - } - - currentSubscription.Dispose(); - currentSubscription = null; - } - } - - public void WakeUp() - { - currentSubscription?.Subscription.WakeUp(); - } - - public ValueTask CompleteAsync() - { - return currentSubscription?.Subscription.CompleteAsync() ?? default; - } - - async ValueTask IEventSubscriber.OnNextAsync(IEventSubscription subscription, T @event) - { - if (!ReferenceEquals(subscription, currentSubscription?.Subscription)) - { - return; - } - - await eventSubscriber.OnNextAsync(this, @event); - } - - async ValueTask IEventSubscriber.OnErrorAsync(IEventSubscription subscription, Exception exception) - { - if (exception is OperationCanceledException) - { - return; - } - - if (!ReferenceEquals(subscription, currentSubscription?.Subscription)) - { - return; - } - - Unsubscribe(); - - if (!retryWindow.CanRetryAfterFailure()) - { - await eventSubscriber.OnErrorAsync(this, exception); - return; - } - - try - { - await Task.Delay(ReconnectWaitMs, currentSubscription?.Cancellation?.Token ?? default); - } - catch (OperationCanceledException) - { - return; - } - - Subscribe(); - } -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/StoredEvent.cs b/backend/src/Squidex.Infrastructure/EventSourcing/StoredEvent.cs deleted file mode 100644 index 88811dbcca..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/StoredEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable SA1313 // Parameter names should begin with lower-case letter - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed record StoredEvent(string StreamName, string EventPosition, long EventStreamNumber, EventData Data); diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilter.cs b/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilter.cs deleted file mode 100644 index 4b907bc967..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.Collections; - -namespace Squidex.Infrastructure.EventSourcing; - -public readonly record struct StreamFilter -{ - public ReadonlyList? Prefixes { get; } - - public StreamFilterKind Kind { get; } - - public StreamFilter(StreamFilterKind kind, params string[] prefixes) - { - Kind = kind; - - if (prefixes.Length > 0) - { - Prefixes = prefixes.ToReadonlyList(); - } - } - - public static StreamFilter Prefix(params string[] prefixes) - { - return new StreamFilter(StreamFilterKind.MatchStart, prefixes); - } - - public static StreamFilter Name(params string[] prefixes) - { - return new StreamFilter(StreamFilterKind.MatchFull, prefixes); - } - - public static StreamFilter All() - { - return default; - } -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilterKind.cs b/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilterKind.cs deleted file mode 100644 index f52fda22cb..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/StreamFilterKind.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -public enum StreamFilterKind -{ - MatchFull, - MatchStart -} diff --git a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs b/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs deleted file mode 100644 index 15334bdf4a..0000000000 --- a/backend/src/Squidex.Infrastructure/EventSourcing/WrongEventVersionException.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -[Serializable] -public class WrongEventVersionException(long versionCurrent, long versionExpected, Exception? inner = null) : Exception(FormatMessage(versionCurrent, versionExpected), inner) -{ - public long VersionCurrent { get; } = versionCurrent; - - public long VersionExpected { get; } = versionExpected; - - private static string FormatMessage(long currentVersion, long expectedVersion) - { - return $"Requested version {expectedVersion}, but found {currentVersion}."; - } -} diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index e3e435aa9f..94bfb31093 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -24,12 +24,13 @@ - - - - - - + + + + + + + diff --git a/backend/src/Squidex.Infrastructure/States/BatchContext.cs b/backend/src/Squidex.Infrastructure/States/BatchContext.cs index b400ff8493..b5f801b6e3 100644 --- a/backend/src/Squidex.Infrastructure/States/BatchContext.cs +++ b/backend/src/Squidex.Infrastructure/States/BatchContext.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; #pragma warning disable RECS0108 // Warns about static fields in generic types diff --git a/backend/src/Squidex.Infrastructure/States/Persistence.cs b/backend/src/Squidex.Infrastructure/States/Persistence.cs index 7163b8fac1..996b5aa734 100644 --- a/backend/src/Squidex.Infrastructure/States/Persistence.cs +++ b/backend/src/Squidex.Infrastructure/States/Persistence.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; #pragma warning disable RECS0012 // 'if' statement can be re-written as 'switch' statement diff --git a/backend/src/Squidex.Infrastructure/States/Store.cs b/backend/src/Squidex.Infrastructure/States/Store.cs index 40edf29dde..c08d6613ed 100644 --- a/backend/src/Squidex.Infrastructure/States/Store.cs +++ b/backend/src/Squidex.Infrastructure/States/Store.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.States; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index eef603a905..c7972db505 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -10,7 +10,7 @@ using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Config.OpenApi; using Squidex.Areas.Api.Controllers.Assets.Models; -using Squidex.Assets; +using Squidex.Assets.TusAdapter; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs index 0bc4355d5a..a9bffa2ffe 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Assets/Models/UpsertAssetDto.cs @@ -6,7 +6,7 @@ // ========================================================================== using Microsoft.AspNetCore.Mvc; -using Squidex.Assets; +using Squidex.Assets.TusAdapter; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Setup/SetupController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Setup/SetupController.cs index a2611b5ca1..e002a2fc67 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Setup/SetupController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Setup/SetupController.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Options; using Squidex.Areas.Api.Controllers.UI; using Squidex.Assets; +using Squidex.Assets.FTP; using Squidex.Config; using Squidex.Domain.Users; using Squidex.Hosting; diff --git a/backend/src/Squidex/Config/Domain/AssetServices.cs b/backend/src/Squidex/Config/Domain/AssetServices.cs index 5f76c34c94..fe69550d2f 100644 --- a/backend/src/Squidex/Config/Domain/AssetServices.cs +++ b/backend/src/Squidex/Config/Domain/AssetServices.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using MongoDB.Driver.GridFS; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets.Queries; @@ -128,20 +127,7 @@ public static void AddSquidexAssetInfrastructure(this IServiceCollection service }, ["MongoDb"] = () => { - var mongoConfiguration = config.GetRequiredValue("assetStore:mongoDb:configuration"); - var mongoDatabaseName = config.GetRequiredValue("assetStore:mongoDb:database"); - var mongoGridFsBucketName = config.GetRequiredValue("assetStore:mongoDb:bucket"); - - services.AddMongoAssetStore(c => - { - var mongoClient = StoreServices.GetMongoClient(mongoConfiguration); - var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); - - return new GridFSBucket(mongoDatabase, new GridFSBucketOptions - { - BucketName = mongoGridFsBucketName - }); - }); + services.AddSquidexMongoAssetStore(config); } }); } diff --git a/backend/src/Squidex/Config/Domain/EventSourcingServices.cs b/backend/src/Squidex/Config/Domain/EventSourcingServices.cs index f832c6264e..09eb6f6482 100644 --- a/backend/src/Squidex/Config/Domain/EventSourcingServices.cs +++ b/backend/src/Squidex/Config/Domain/EventSourcingServices.cs @@ -6,8 +6,8 @@ // ========================================================================== using EventStore.Client; +using Squidex.Events.GetEventStore; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Consume; using Squidex.Infrastructure.States; @@ -22,17 +22,7 @@ public static void AddSquidexEventSourcing(this IServiceCollection services, ICo { ["MongoDb"] = () => { - var mongoConfiguration = config.GetRequiredValue("eventStore:mongoDb:configuration"); - var mongoDatabaseName = config.GetRequiredValue("eventStore:mongoDb:database"); - - services.AddSingletonAs(c => - { - var mongoClient = StoreServices.GetMongoClient(mongoConfiguration); - var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName); - - return new MongoEventStore(mongoDatabase); - }) - .As(); + services.AddSquidexMongoEventStore(config); }, ["GetEventStore"] = () => { @@ -41,11 +31,7 @@ public static void AddSquidexEventSourcing(this IServiceCollection services, ICo services.AddSingletonAs(_ => EventStoreClientSettings.Create(configuration)) .AsSelf(); - services.AddSingletonAs() - .As(); - - services.AddHealthChecks() - .AddCheck("EventStore", tags: ["node"]); + services.AddGetEventStore(config); } }); diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index 8365e8453b..5475e56aff 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -143,17 +143,18 @@ public static void AddSquidexTranslation(this IServiceCollection services, IConf if (!string.IsNullOrWhiteSpace(apiKey)) { - services.AddOpenAIChat(config); - services.AddAIImagePipe(); - services.AddDallE(config, options => - { - options.DownloadImage = true; - - if (string.IsNullOrEmpty(options.ApiKey)) + services.AddAI() + .AddOpenAIChat(config) + .AddAIImagePipe() + .AddDallE(config, options => { - options.ApiKey = apiKey; - } - }); + options.DownloadImage = true; + + if (string.IsNullOrEmpty(options.ApiKey)) + { + options.ApiKey = apiKey; + } + }); } services.AddDeepLTranslations(config); diff --git a/backend/src/Squidex/Config/Domain/ResizeServices.cs b/backend/src/Squidex/Config/Domain/ResizeServices.cs index c3e16516c3..9dc60a822f 100644 --- a/backend/src/Squidex/Config/Domain/ResizeServices.cs +++ b/backend/src/Squidex/Config/Domain/ResizeServices.cs @@ -6,6 +6,8 @@ // ========================================================================== using Squidex.Assets; +using Squidex.Assets.ImageMagick; +using Squidex.Assets.ImageSharp; using Squidex.Assets.Remote; namespace Squidex.Config.Domain; diff --git a/backend/src/Squidex/Config/Domain/SerializationServices.cs b/backend/src/Squidex/Config/Domain/SerializationServices.cs index 680e895616..dd6b7b2943 100644 --- a/backend/src/Squidex/Config/Domain/SerializationServices.cs +++ b/backend/src/Squidex/Config/Domain/SerializationServices.cs @@ -26,6 +26,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; +using Squidex.Events.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; @@ -48,6 +49,7 @@ private static JsonSerializerOptions ConfigureJson(TypeRegistry typeRegistry, Js options.Converters.Add(new StringConverter(x => x)); options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); + options.Converters.Add(new HeaderValueConverter()); options.Converters.Add(new GeoJsonConverterFactory()); options.Converters.Add(new PolymorphicConverter(typeRegistry)); options.Converters.Add(new PolymorphicConverter(typeRegistry)); diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 4d759c239a..7d9e41e9bf 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -5,52 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Net; -using System.Text.Json; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Caching.Distributed; using Migrations.Migrations.MongoDb; -using MongoDB.Bson; using MongoDB.Driver; -using MongoDB.Driver.Core.Extensions.DiagnosticSources; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.Teams; -using Squidex.Domain.Apps.Entities; -using Squidex.Domain.Apps.Entities.Apps.Repositories; -using Squidex.Domain.Apps.Entities.Assets.Repositories; -using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.Contents.Text; -using Squidex.Domain.Apps.Entities.Contents.Text.State; -using Squidex.Domain.Apps.Entities.History.Repositories; -using Squidex.Domain.Apps.Entities.MongoDb.Apps; -using Squidex.Domain.Apps.Entities.MongoDb.Assets; -using Squidex.Domain.Apps.Entities.MongoDb.Contents; -using Squidex.Domain.Apps.Entities.MongoDb.History; -using Squidex.Domain.Apps.Entities.MongoDb.Rules; -using Squidex.Domain.Apps.Entities.MongoDb.Schemas; -using Squidex.Domain.Apps.Entities.MongoDb.Teams; -using Squidex.Domain.Apps.Entities.MongoDb.Text; -using Squidex.Domain.Apps.Entities.Rules.Repositories; -using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Domain.Apps.Entities.Schemas.Repositories; -using Squidex.Domain.Apps.Entities.Teams.Repositories; -using Squidex.Domain.Users; -using Squidex.Domain.Users.InMemory; -using Squidex.Domain.Users.MongoDb; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.Diagnostics; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; -using Squidex.Infrastructure.UsageTracking; -using YDotNet.Server.MongoDB; namespace Squidex.Config.Domain; @@ -62,49 +20,27 @@ public static void AddSquidexStoreServices(this IServiceCollection services, ICo { ["MongoDB"] = () => { - var mongoConfiguration = config.GetRequiredValue("store:mongoDb:configuration")!; var mongoDatabaseName = config.GetRequiredValue("store:mongoDb:database")!; var mongoContentDatabaseName = config.GetOptionalValue("store:mongoDb:contentDatabase", mongoDatabaseName)!; - services.AddMongoAssetKeyValueStore(); - services.AddSingleton(typeof(ISnapshotStore<>), typeof(MongoSnapshotStore<>)); - - services.AddYDotNet() - .AddMongoStorage(options => - { - options.DatabaseName = mongoDatabaseName; - }); - - services.AddMongoChatStore(config, options => - { - options.CollectionName = "Chat"; - }); - - services.AddMessaging() - .AddMongoDataStore(config); - - services.AddSingletonAs(c => GetMongoClient(mongoConfiguration)) - .As(); - - services.AddSingletonAs(c => GetDatabase(c, mongoDatabaseName)) - .As(); - - services.AddSingletonAs() - .As(); + static IMongoDatabase GetDatabase(IServiceProvider serviceProvider, string name) + { + return serviceProvider.GetRequiredService().GetDatabase(name); + } - services.AddTransientAs() + services.AddTransientAs(c => new DeleteContentCollections(GetDatabase(c, mongoContentDatabaseName))) .As(); - services.AddTransientAs() + services.AddTransientAs(c => new RestructureContentCollection(GetDatabase(c, mongoContentDatabaseName))) .As(); - services.AddTransientAs(c => new DeleteContentCollections(GetDatabase(c, mongoContentDatabaseName))) + services.AddTransientAs(c => new ConvertDocumentIds(GetDatabase(c, mongoDatabaseName), GetDatabase(c, mongoContentDatabaseName))) .As(); - services.AddTransientAs(c => new RestructureContentCollection(GetDatabase(c, mongoContentDatabaseName))) + services.AddTransientAs() .As(); - services.AddTransientAs(c => new ConvertDocumentIds(GetDatabase(c, mongoDatabaseName), GetDatabase(c, mongoContentDatabaseName))) + services.AddTransientAs() .As(); services.AddTransientAs() @@ -119,115 +55,7 @@ public static void AddSquidexStoreServices(this IServiceCollection services, ICo services.AddTransientAs() .As(); - services.AddSingletonAs() - .As(); - - services.AddHealthChecks() - .AddCheck("MongoDB", tags: ["node"]); - - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As().As(); - - services.AddSingletonAs() - .As().As(); - - services.AddSingletonAs() - .As>(); - - services.AddSingletonAs() - .As>().As(); - - services.AddSingletonAs() - .As().As>().As(); - - services.AddSingletonAs() - .As().As>().As(); - - services.AddSingletonAs() - .As().As>(); - - services.AddSingletonAs() - .As().As>().As(); - - services.AddSingletonAs() - .As().As>().As(); - - services.AddSingletonAs() - .AsOptional().As().As(); - - services.AddSingletonAs() - .As().As(); - - services.AddSingletonAs(c => - { - return new MongoShardedAssetRepository(GetSharding(config, "store:mongoDB:assetShardCount"), - shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); - }).As().As>().As(); - - services.AddSingletonAs(c => - { - var contentDatabase = GetDatabase(c, mongoContentDatabaseName); - - return new MongoShardedContentRepository(GetSharding(config, "store:mongoDB:contentShardCount"), - shardKey => ActivatorUtilities.CreateInstance(c, shardKey, contentDatabase)); - }).As().As>().As(); - - services.AddOpenIddict() - .AddCore(builder => - { - builder.UseMongoDb() - .SetScopesCollectionName("Identity_Scopes") - .SetTokensCollectionName("Identity_Tokens"); - - builder.SetDefaultScopeEntity(); - builder.SetDefaultApplicationEntity(); - }); - - var atlasOptions = config.GetSection("store:mongoDb:atlas").Get() ?? new (); - - if (atlasOptions.IsConfigured() && atlasOptions.FullTextEnabled) - { - services.Configure(config.GetSection("store:mongoDb:atlas")); - - services.AddHttpClient("Atlas", options => - { - options.BaseAddress = new Uri("https://cloud.mongodb.com/"); - }) - .ConfigurePrimaryHttpMessageHandler(() => - { - return new HttpClientHandler - { - Credentials = new NetworkCredential(atlasOptions.PublicKey, atlasOptions.PrivateKey, "cloud.mongodb.com") - }; - }); - - services.AddSingletonAs(c => - { - return new MongoShardedTextIndex>(GetSharding(config, "store:mongoDB:textShardCount"), - shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); - }).AsOptional().As(); - } - else - { - services.AddSingletonAs(c => - { - return new MongoShardedTextIndex>(GetSharding(config, "store:mongoDB:textShardCount"), - shardKey => ActivatorUtilities.CreateInstance(c, shardKey)); - }).AsOptional().As(); - } - - services.AddInitializer("Serializer (BSON)", jsonSerializerOptions => - { - var representation = config.GetValue("store:mongoDB:valueRepresentation"); - - BsonJsonConvention.Register(jsonSerializerOptions, representation); - }, int.MinValue); + services.AddSquidexMongoStore(config); } }); @@ -237,30 +65,4 @@ public static void AddSquidexStoreServices(this IServiceCollection services, ICo services.AddSingleton(typeof(IPersistenceFactory<>), typeof(Store<>)); } - - public static IMongoClient GetMongoClient(string configuration) - { - return Singletons.GetOrAdd(configuration, connectionString => - { - return MongoClientFactory.Create(connectionString, settings => - { - settings.ClusterConfigurator = builder => - { - builder.Subscribe(new DiagnosticsActivityEventSubscriber()); - }; - }); - }); - } - - private static IShardingStrategy GetSharding(IConfiguration config, string name) - { - var shardCount = config.GetValue(name); - - return shardCount > 0 && shardCount <= 100 ? new PartitionedSharding(shardCount) : SingleSharding.Instance; - } - - private static IMongoDatabase GetDatabase(IServiceProvider serviceProvider, string name) - { - return serviceProvider.GetRequiredService().GetDatabase(name); - } } diff --git a/backend/src/Squidex/Config/Messaging/MessagingServices.cs b/backend/src/Squidex/Config/Messaging/MessagingServices.cs index 00be684717..c001c514d1 100644 --- a/backend/src/Squidex/Config/Messaging/MessagingServices.cs +++ b/backend/src/Squidex/Config/Messaging/MessagingServices.cs @@ -43,7 +43,8 @@ public static void AddSquidexMessaging(this IServiceCollection services, IConfig if (isWorker) { - services.AddAICleaner(); + services.AddAI() + .AddCleaner(); services.AddSingletonAs() .AsSelf(); diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index 2656e725ce..7f29af7d59 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -18,16 +18,13 @@ + - - - - @@ -54,8 +51,6 @@ - - @@ -66,24 +61,22 @@ - - - - - - - - + + + + + + + - - - - + + + + - diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetMappingTests.cs similarity index 92% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetMappingTests.cs index bb220c4a0b..a64176827d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetMappingTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetMappingTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Assets; -using Squidex.Domain.Apps.Entities.MongoDb.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; +namespace Squidex.MongoDb.Domain.Assets; public class AssetMappingTests : GivenContext { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetQueryTests.cs similarity index 93% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetQueryTests.cs index 2409fb732b..d27abdc084 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetQueryTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetQueryTests.cs @@ -8,17 +8,15 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; using NodaTime.Text; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.MongoDb.Assets; -using Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Visitors; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Validation; +using Squidex.TestHelpers; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; -using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; -namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; +namespace Squidex.MongoDb.Domain.Assets; public class AssetQueryTests { @@ -28,7 +26,7 @@ static AssetQueryTests() { MongoAssetEntity.RegisterClassMap(); - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); } [Fact] @@ -201,7 +199,7 @@ private void AssertQuery(string expected, FilterNode filter, object? a private void AssertQuery(string expected, ClrQuery query, object? arg = null) { - var filter = query.AdjustToModel(appId).BuildFilter(false).Filter!; + var filter = query.AdjustToAssetModel(appId).BuildFilter(false).Filter!; var rendered = filter.Render( @@ -230,7 +228,7 @@ private void AssertSorting(string expected, params SortNode[] sort) .ToString(); }); - cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToModel(appId)); + cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToAssetModel(appId)); Assert.Equal(Cleanup(expected), rendered); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryFixture.cs similarity index 97% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryFixture.cs index 735790b00a..b0010ab81e 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryFixture.cs @@ -14,10 +14,9 @@ using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; +namespace Squidex.MongoDb.Domain.Assets; public sealed class AssetsQueryFixture : GivenContext, IAsyncLifetime { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryIntegrationTests.cs similarity index 95% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryIntegrationTests.cs index de15c7fba5..19634351f9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Assets/AssetsQueryIntegrationTests.cs @@ -6,14 +6,16 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Assets; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; using F = Squidex.Infrastructure.Queries.ClrFilter; +#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one #pragma warning disable SA1300 // Element should begin with upper-case letter +#pragma warning disable xUnit1044 // Avoid using TheoryData type arguments that are not serializable -namespace Squidex.Domain.Apps.Entities.Assets.MongoDb; +namespace Squidex.MongoDb.Domain.Assets; [Trait("Category", "Dependencies")] public class AssetsQueryIntegrationTests : IClassFixture, IAsyncLifetime diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/AdaptionTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/AdaptionTests.cs similarity index 88% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/AdaptionTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/AdaptionTests.cs index 46db9eddfe..eee06fb19f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/AdaptionTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/AdaptionTests.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.MongoDb.Contents; -using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Operations; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class AdaptionTests { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentMappingTests.cs similarity index 96% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentMappingTests.cs index 6c1aaf9115..3911e62811 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentMappingTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentMappingTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Entities.MongoDb.Contents; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class ContentMappingTests : GivenContext { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentQueryTests.cs similarity index 94% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentQueryTests.cs index 43cb6eba66..e5fc46fe8d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentQueryTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentQueryTests.cs @@ -10,17 +10,15 @@ using NodaTime.Text; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.MongoDb.Contents; -using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Operations; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; +using Squidex.TestHelpers; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; -using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class ContentQueryTests : GivenContext { @@ -28,7 +26,7 @@ static ContentQueryTests() { MongoContentEntity.RegisterClassMap(); - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); } public ContentQueryTests() @@ -223,7 +221,7 @@ private void AssertQuery(string expected, FilterNode filter, object? a private void AssertQuery(ClrQuery query, string expected, object? arg = null) { - var filter = query.AdjustToModel(AppId.Id).BuildFilter(false).Filter!; + var filter = query.AdjustToContentModel(AppId.Id).BuildFilter(false).Filter!; var rendered = filter.Render( @@ -251,7 +249,7 @@ private void AssertSorting(string expected, params SortNode[] sort) .ToString(); }); - cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToModel(AppId.Id)); + cursor.QuerySort(new ClrQuery { Sort = sort.ToList() }.AdjustToContentModel(AppId.Id)); Assert.Equal(Cleanup(expected), rendered); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryDedicatedIntegrationTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryDedicatedIntegrationTests.cs similarity index 74% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryDedicatedIntegrationTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryDedicatedIntegrationTests.cs index 42617f1a37..b6666129a1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryDedicatedIntegrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryDedicatedIntegrationTests.cs @@ -5,9 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; [Trait("Category", "Dependencies")] -public class ContentsQueryDedicatedIntegrationTests(ContentsQueryFixture_Dedicated fixture) : ContentsQueryTestsBase(fixture), IClassFixture +public class ContentsQueryDedicatedIntegrationTests(ContentsQueryFixture_Dedicated fixture) + : ContentsQueryTestsBase(fixture), IClassFixture { } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryFixture.cs similarity index 98% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryFixture.cs index 21cfaf98c7..6b3f9e9969 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryFixture.cs @@ -14,16 +14,17 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; #pragma warning disable MA0048 // File name must match type name -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public sealed class ContentsQueryFixture_Default : ContentsQueryFixture { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryIntegrationTests.cs similarity index 75% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryIntegrationTests.cs index dc088c0000..db78bb2f47 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryIntegrationTests.cs @@ -5,9 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; [Trait("Category", "Dependencies")] -public class ContentsQueryIntegrationTests(ContentsQueryFixture_Default fixture) : ContentsQueryTestsBase(fixture), IClassFixture +public class ContentsQueryIntegrationTests(ContentsQueryFixture_Default fixture) + : ContentsQueryTestsBase(fixture), IClassFixture { } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryTestsBase.cs similarity index 98% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryTestsBase.cs index 46665b7cbb..f1068c0f86 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryTestsBase.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ContentsQueryTestsBase.cs @@ -7,6 +7,8 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; @@ -14,7 +16,7 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public abstract class ContentsQueryTestsBase(ContentsQueryFixture fixture) { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ExtensionsTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ExtensionsTests.cs similarity index 95% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ExtensionsTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ExtensionsTests.cs index ebabdc4a03..18cc8be2bf 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ExtensionsTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/ExtensionsTests.cs @@ -7,10 +7,10 @@ using MongoDB.Bson.Serialization; using MongoDB.Driver; -using Squidex.Domain.Apps.Entities.MongoDb.Contents; -using ExtensionSut = Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations.Extensions; +using Squidex.Domain.Apps.Entities.Contents; +using ExtensionSut = Squidex.Domain.Apps.Entities.Contents.Operations.Extensions; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class ExtensionsTests { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/IndexParserTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/IndexParserTests.cs similarity index 96% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/IndexParserTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/IndexParserTests.cs index b642ab2a2c..9d8674a27a 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/IndexParserTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/IndexParserTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using MongoDB.Bson; -using Squidex.Domain.Apps.Entities.MongoDb.Contents; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class IndexParserTests { diff --git a/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/StatusSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/StatusSerializerTests.cs new file mode 100644 index 0000000000..a1011ff1a1 --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/StatusSerializerTests.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Contents; +using Squidex.TestHelpers; + +namespace Squidex.MongoDb.Domain.Contents; + +public sealed class StatusSerializerTests +{ + [Fact] + public void Should_serialize_and_deserialize_status() + { + var source = Status.Published; + + var deserialized = source.SerializeAndDeserializeBson(); + + Assert.Equal(source, deserialized); + } + + [Fact] + public void Should_serialize_and_deserialize_default_status() + { + var source = default(Status); + + var deserialized = source.SerializeAndDeserializeBson(); + + Assert.Equal(source, deserialized); + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasParsingTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasParsingTests.cs similarity index 99% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasParsingTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasParsingTests.cs index 2fd2ef134e..eec0a402fe 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasParsingTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasParsingTests.cs @@ -11,10 +11,10 @@ using Lucene.Net.Analysis.Util; using Lucene.Net.Util; using MongoDB.Bson; -using Squidex.Domain.Apps.Entities.MongoDb.Text; +using Squidex.Domain.Apps.Entities.Text; using LuceneQueryAnalyzer = Lucene.Net.QueryParsers.Classic.QueryParser; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; public class AtlasParsingTests { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexFixture.cs similarity index 90% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexFixture.cs index 68806e5755..87b4e04bf8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexFixture.cs @@ -9,12 +9,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.MongoDb.Text; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.MongoDb; +using Squidex.Domain.Apps.Entities.Text; +using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; public sealed class AtlasTextIndexFixture : IAsyncLifetime { @@ -22,7 +22,7 @@ public sealed class AtlasTextIndexFixture : IAsyncLifetime public AtlasTextIndexFixture() { - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["atlas:configuration"]!); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["atlas:database"]!); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexTests.cs similarity index 88% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexTests.cs index 2d2bb91e82..5b6769c850 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AtlasTextIndexTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/AtlasTextIndexTests.cs @@ -7,7 +7,9 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Domain.Apps.Entities.Contents.Text; +using Squidex.Domain.Apps.Entities.Contents.Text; + +namespace Squidex.MongoDb.Domain.Contents.Text; [Trait("Category", "Dependencies")] public class AtlasTextIndexTests(AtlasTextIndexFixture fixture) : TextIndexerTestsBase, IClassFixture @@ -20,9 +22,9 @@ public class AtlasTextIndexTests(AtlasTextIndexFixture fixture) : TextIndexerTes public AtlasTextIndexFixture _ { get; } = fixture; - public override ITextIndex CreateIndex() + public override Task CreateSutAsync() { - return _.Index; + return Task.FromResult(_.Index); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/BsonUniqueContentIdSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/BsonUniqueContentIdSerializerTests.cs similarity index 97% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/BsonUniqueContentIdSerializerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/BsonUniqueContentIdSerializerTests.cs index c4e8485240..ed80d9f695 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/BsonUniqueContentIdSerializerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/BsonUniqueContentIdSerializerTests.cs @@ -5,11 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.MongoDb; using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; public class BsonUniqueContentIdSerializerTests { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexFixture.cs similarity index 56% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexFixture.cs index 62eaa1df3c..7762acaaf7 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexFixture.cs @@ -5,34 +5,43 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.MongoDb.Text; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.MongoDb; +using Squidex.Domain.Apps.Entities.Text; +using Squidex.Infrastructure; +using Squidex.TestHelpers; +using Testcontainers.MongoDb; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; public sealed class MongoTextIndexFixture : IAsyncLifetime { - public MongoTextIndex Index { get; } + private readonly MongoDbContainer mongoDb = + new MongoDbBuilder() + .WithReuse(true) + .WithLabel("reuse-id", "mongo-text-index") + .Build(); + + public MongoTextIndex Index { get; private set; } public MongoTextIndexFixture() { - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); + } + + public async Task InitializeAsync() + { + await mongoDb.StartAsync(); var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]!); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]!); Index = new MongoTextIndex(mongoDatabase, string.Empty); - } - public Task InitializeAsync() - { - return Index.InitializeAsync(default); + await Index.InitializeAsync(default); } - public Task DisposeAsync() + public async Task DisposeAsync() { - return Task.CompletedTask; + await mongoDb.StopAsync(); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexTests.cs similarity index 86% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexTests.cs index 5583eca6cf..1e362463ab 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexTests.cs @@ -7,7 +7,9 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Domain.Apps.Entities.Contents.Text; +using Squidex.Domain.Apps.Entities.Contents.Text; + +namespace Squidex.MongoDb.Domain.Contents.Text; [Trait("Category", "Dependencies")] public class MongoTextIndexTests(MongoTextIndexFixture fixture) : TextIndexerTestsBase, IClassFixture @@ -18,9 +20,9 @@ public class MongoTextIndexTests(MongoTextIndexFixture fixture) : TextIndexerTes public MongoTextIndexFixture _ { get; } = fixture; - public override ITextIndex CreateIndex() + public override Task CreateSutAsync() { - return _.Index; + return Task.FromResult(_.Index); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateFixture.cs similarity index 60% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateFixture.cs index d36c8a04c6..5a783b9829 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateFixture.cs @@ -5,37 +5,46 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.Contents.Repositories; -using Squidex.Domain.Apps.Entities.MongoDb.Text; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.MongoDb; +using Squidex.Domain.Apps.Entities.Text; +using Squidex.Infrastructure; +using Squidex.TestHelpers; +using Testcontainers.MongoDb; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; public sealed class MongoTextIndexerStateFixture : IAsyncLifetime { + private readonly MongoDbContainer mongoDb = + new MongoDbBuilder() + .WithReuse(true) + .WithLabel("reuse-id", "mongo-text-indexer") + .Build(); + public IContentRepository ContentRepository { get; } = A.Fake(); - public MongoTextIndexerState State { get; } + public MongoTextIndexerState State { get; private set; } public MongoTextIndexerStateFixture() { - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); + } + + public async Task InitializeAsync() + { + await mongoDb.StartAsync(); var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]!); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]!); State = new MongoTextIndexerState(mongoDatabase, ContentRepository); - } - public Task InitializeAsync() - { - return State.InitializeAsync(default); + await State.InitializeAsync(default); } - public Task DisposeAsync() + public async Task DisposeAsync() { - return Task.CompletedTask; + await mongoDb.DisposeAsync(); } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateTests.cs similarity index 96% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateTests.cs index 001b73e417..78d2c577f8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/MongoTextIndexerStateTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/Text/MongoTextIndexerStateTests.cs @@ -9,10 +9,13 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Text; using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Entities.Contents.Text; +namespace Squidex.MongoDb.Domain.Contents.Text; [Trait("Category", "Dependencies")] public class MongoTextIndexerStateTests(MongoTextIndexerStateFixture fixture) : IClassFixture @@ -35,6 +38,7 @@ await _.State.SetAsync( ]); var actual = await _.State.GetAsync(HashSet.Of(id1, id2)); + actual.Should().BeEquivalentTo(new Dictionary { [id1] = new TextContentState { UniqueContentId = id1, State = TextState.Stage0_Draft__Stage1_None }, @@ -83,6 +87,7 @@ await _.State.SetAsync( await ((IDeleter)_.State).DeleteAppAsync(app2, default); var actual = await _.State.GetAsync(HashSet.Of(id1, id2, id3)); + actual.Should().BeEquivalentTo(new Dictionary { [id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } @@ -115,6 +120,7 @@ await _.State.SetAsync( await ((IDeleter)_.State).DeleteSchemaAsync(app, schema, default); var actual = await _.State.GetAsync(HashSet.Of(id1, id2, id3)); + actual.Should().BeEquivalentTo(new Dictionary { [id3] = new TextContentState { UniqueContentId = id3, State = TextState.Stage0_Published__Stage1_None } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/TokenizerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/TokenizerTests.cs similarity index 94% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/TokenizerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/TokenizerTests.cs index 11b8f9aa24..e9d40f20dd 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/TokenizerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Contents/TokenizerTests.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Entities.MongoDb.Text; +using Squidex.Domain.Apps.Entities.Text; -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; +namespace Squidex.MongoDb.Domain.Contents; public class TokenizerTests { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashFixture.cs similarity index 81% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashFixture.cs index 18869e3bf7..5d2fdb3763 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashFixture.cs @@ -5,12 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Domain.Apps.Core.TestHelpers; -using Squidex.Domain.Apps.Entities.MongoDb.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Domain.Apps.Entities.Schemas.MongoDb; +namespace Squidex.MongoDb.Domain.Schemas; public sealed class SchemasHashFixture { @@ -18,7 +18,7 @@ public sealed class SchemasHashFixture public SchemasHashFixture() { - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); var mongoClient = MongoClientFactory.Create(TestConfig.Configuration["mongoDb:configuration"]); var mongoDatabase = mongoClient.GetDatabase(TestConfig.Configuration["mongodb:database"]); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashTests.cs similarity index 97% rename from backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashTests.cs index 746a947c91..2883ddaaf8 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/MongoDb/SchemasHashTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Domain/Schemas/SchemasHashTests.cs @@ -8,6 +8,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; @@ -15,7 +16,7 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Domain.Apps.Entities.Schemas.MongoDb; +namespace Squidex.MongoDb.Domain.Schemas; [Trait("Category", "Dependencies")] public class SchemasHashTests : GivenContext, IClassFixture diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/BsonJsonSerializerTests.cs similarity index 95% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/BsonJsonSerializerTests.cs index 1108fa06db..01a4acce66 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/BsonJsonSerializerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/BsonJsonSerializerTests.cs @@ -7,40 +7,41 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Squidex.Infrastructure.TestHelpers; +using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class BsonJsonSerializerTests { - public class TestWrapper + private class TestWrapper { [BsonJson] public T Value { get; set; } } - public class TestWrapperDocument + private class TestWrapperDocument { [BsonJson] [BsonRepresentation(BsonType.Document)] public T Value { get; set; } } - public class TestWrapperString + private class TestWrapperString { [BsonJson] [BsonRepresentation(BsonType.String)] public T Value { get; set; } } - public class TestWrapperBinary + private class TestWrapperBinary { [BsonJson] [BsonRepresentation(BsonType.Binary)] public T Value { get; set; } } - public class TestObject + private class TestObject { public bool Bool { get; set; } diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/DomainIdSerializerTests.cs similarity index 75% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/DomainIdSerializerTests.cs index ae5646675e..93330372dd 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/DomainIdSerializerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/DomainIdSerializerTests.cs @@ -7,9 +7,10 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Squidex.Infrastructure.TestHelpers; +using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class DomainIdSerializerTests { @@ -30,17 +31,12 @@ private sealed class IdEntity public T Id { get; set; } } - public DomainIdSerializerTests() - { - TestUtils.SetupBson(); - } - [Fact] public void Should_deserialize_from_string() { var source = new IdEntity { Id = Guid.NewGuid().ToString() }; - var actual = TestUtils.SerializeAndDeserializeBson, IdEntity>(source); + var actual = MongoTestUtils.SerializeAndDeserializeBson, IdEntity>(source); Assert.Equal(actual.Id.ToString(), source.Id); } @@ -50,7 +46,7 @@ public void Should_deserialize_from_guid_string() { var source = new StringEntity { Id = Guid.NewGuid() }; - var actual = TestUtils.SerializeAndDeserializeBson, StringEntity>(source); + var actual = MongoTestUtils.SerializeAndDeserializeBson, StringEntity>(source); Assert.Equal(actual.Id.ToString(), source.Id.ToString()); } @@ -60,7 +56,7 @@ public void Should_deserialize_from_guid_bytes() { var source = new IdEntity { Id = Guid.NewGuid() }; - var actual = TestUtils.SerializeAndDeserializeBson, IdEntity>(source); + var actual = MongoTestUtils.SerializeAndDeserializeBson, IdEntity>(source); Assert.Equal(actual.Id.ToString(), source.Id.ToString()); } @@ -70,7 +66,7 @@ public void Should_serialize_guid_as_bytes() { var source = new BinaryEntity { Id = DomainId.NewGuid() }; - var actual = TestUtils.SerializeAndDeserializeBson(source); + var actual = MongoTestUtils.SerializeAndDeserializeBson(source); Assert.Equal(actual.Id, source.Id); } @@ -80,7 +76,7 @@ public void Should_serialize_non_guid_as_bytes() { var source = new BinaryEntity { Id = DomainId.Create("NonGuid") }; - var actual = TestUtils.SerializeAndDeserializeBson(source); + var actual = MongoTestUtils.SerializeAndDeserializeBson(source); Assert.Equal(actual.Id, source.Id); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorIntegrationTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/EventConsumerProcessorIntegrationTests.cs similarity index 90% rename from backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorIntegrationTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/EventConsumerProcessorIntegrationTests.cs index 8e3e6ad881..87cd85244f 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorIntegrationTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/EventConsumerProcessorIntegrationTests.cs @@ -7,12 +7,17 @@ using Microsoft.Extensions.DependencyInjection; using Squidex.Caching; -using Squidex.Infrastructure.MongoDb; +using Squidex.Events; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.EventSourcing.Consume; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; using Squidex.Infrastructure.TestHelpers; -namespace Squidex.Infrastructure.EventSourcing.Consume; +#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one + +namespace Squidex.MongoDb.Infrastructure; public abstract class EventConsumerProcessorIntegrationTests { @@ -37,6 +42,10 @@ public async Task On(Envelope @event) } } + private class MyEvent : IEvent + { + } + protected IEventStore EventStore { get => store.Value; @@ -115,12 +124,10 @@ await Parallel.ForEachAsync(Enumerable.Range(0, numTasks), async (i, ct) => for (var j = 0; j < numEvents; j++) { -#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one - await persistence.WriteEventsAsync(new List> - { + await persistence.WriteEventsAsync( + [ Envelope.Create(new MyEvent()) - }); -#pragma warning restore MA0040 // Forward the CancellationToken parameter to methods that take one + ]); } }); @@ -131,9 +138,7 @@ await persistence.WriteEventsAsync(new List> { while (!cts.IsCancellationRequested && eventConsumer.Events.Count < expectedEvents) { -#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one await Task.Delay(100); -#pragma warning restore MA0040 // Forward the CancellationToken parameter to methods that take one } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/InstantSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/InstantSerializerTests.cs similarity index 80% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/InstantSerializerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/InstantSerializerTests.cs index 719378406a..04930a482c 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/InstantSerializerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/InstantSerializerTests.cs @@ -6,21 +6,17 @@ // ========================================================================== using NodaTime; -using Squidex.Infrastructure.TestHelpers; +using Squidex.Infrastructure; +using Squidex.TestHelpers; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class InstantSerializerTests { - public InstantSerializerTests() - { - TestUtils.SetupBson(); - } - [Fact] public void Should_serialize_and_deserialize() { - var source = new Entities.DefaultEntity + var source = new BsonAsDefaultEntity { Value = GetTime() }; @@ -33,7 +29,7 @@ public void Should_serialize_and_deserialize() [Fact] public void Should_serialize_and_deserialize_as_string() { - var source = new Entities.StringEntity + var source = new BsonAsStringEntity { Value = GetTime() }; @@ -46,7 +42,7 @@ public void Should_serialize_and_deserialize_as_string() [Fact] public void Should_serialize_and_deserialize_as_int64() { - var source = new Entities.Int64Entity + var source = new BsonAsInt64Entity { Value = GetTime() }; @@ -59,7 +55,7 @@ public void Should_serialize_and_deserialize_as_int64() [Fact] public void Should_serialize_and_deserialize_as_datetime() { - var source = new Entities.DateTimeEntity + var source = new BsonAsDateTimeEntity { Value = GetTime() }; diff --git a/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/JsonValueSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/JsonValueSerializerTests.cs new file mode 100644 index 0000000000..043ca311e0 --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/JsonValueSerializerTests.cs @@ -0,0 +1,114 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Json.Objects; +using Squidex.TestHelpers; + +namespace Squidex.MongoDb.Infrastructure; + +public class JsonValueSerializerTests +{ + [Fact] + public void Should_serialize_and_deserialize_null() + { + var value = JsonValue.Null; + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_date() + { + var value = JsonValue.Create("2008-09-15T15:53:00"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_string() + { + var value = JsonValue.Create("my-string"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_boolean() + { + var value = JsonValue.Create(true); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_number() + { + var value = JsonValue.Create(123); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_double_number() + { + var value = JsonValue.Create(123.5); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_array() + { + var value = JsonValue.Array(1, 2); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_object() + { + var value = + JsonValue.Object() + .Add("1", 1) + .Add("2", 1); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_complex_object() + { + var value = + JsonValue.Object() + .Add("1", + JsonValue.Array( + JsonValue.Object().Add("1_1", 11), + JsonValue.Object().Add("1_2", 12))) + .Add("2", + JsonValue.Object().Add("2_1", 11)); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } +} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/MongoEventConsumerProcessorIntegrationTests_Direct.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventConsumerProcessorIntegrationTests_Direct.cs similarity index 92% rename from backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/MongoEventConsumerProcessorIntegrationTests_Direct.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventConsumerProcessorIntegrationTests_Direct.cs index 684ed3badc..e6d8f46b29 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/MongoEventConsumerProcessorIntegrationTests_Direct.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventConsumerProcessorIntegrationTests_Direct.cs @@ -7,7 +7,9 @@ #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Infrastructure.EventSourcing.Consume; +using Squidex.Events; + +namespace Squidex.MongoDb.Infrastructure; [Trait("Category", "Dependencies")] public class MongoEventConsumerProcessorIntegrationTests_Direct(MongoEventStoreFixture_Direct fixture) : EventConsumerProcessorIntegrationTests, IClassFixture diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreFixture.cs similarity index 87% rename from backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreFixture.cs index 5825a494ff..50514ffc1a 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreFixture.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreFixture.cs @@ -5,13 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Microsoft.Extensions.Options; using MongoDB.Driver; -using Squidex.Infrastructure.MongoDb; +using Squidex.Events.Mongo; +using Squidex.Infrastructure; using Squidex.Infrastructure.TestHelpers; #pragma warning disable MA0048 // File name must match type name -namespace Squidex.Infrastructure.EventSourcing; +namespace Squidex.MongoDb.Infrastructure; public sealed class MongoEventStoreFixture_Direct : MongoEventStoreFixture { @@ -47,7 +49,7 @@ protected MongoEventStoreFixture(string connectionString) Database = mongoDatabase; - EventStore = new MongoEventStore(mongoDatabase); + EventStore = new MongoEventStore(mongoDatabase, Options.Create(new MongoEventStoreOptions())); } public Task InitializeAsync() diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoParallelInsertTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreParallelInsertTests.cs similarity index 94% rename from backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoParallelInsertTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreParallelInsertTests.cs index 38e8801b1b..b683cd3343 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoParallelInsertTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoEventStoreParallelInsertTests.cs @@ -6,19 +6,22 @@ // ========================================================================== using Microsoft.Extensions.Logging; +using Squidex.Events; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Consume; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.TestHelpers; #pragma warning disable SA1300 // Element should begin with upper-case letter -namespace Squidex.Infrastructure.EventSourcing; +namespace Squidex.MongoDb.Infrastructure; [Trait("Category", "Dependencies")] -public sealed class MongoParallelInsertTests : IClassFixture +public sealed class MongoEventStoreParallelInsertTests : IClassFixture { private readonly TestState state = new TestState(DomainId.Empty); - private readonly IEventFormatter eventFormatter; + private readonly DefaultEventFormatter eventFormatter; public MongoEventStoreFixture _ { get; } @@ -59,7 +62,7 @@ public async Task On(Envelope @event) } } - public MongoParallelInsertTests(MongoEventStoreFixture_Replica fixture) + public MongoEventStoreParallelInsertTests(MongoEventStoreFixture_Replica fixture) { _ = fixture; diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoFieldTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoFieldTests.cs similarity index 92% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoFieldTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoFieldTests.cs index 74fbf67376..19f0563a80 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoFieldTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoFieldTests.cs @@ -6,12 +6,13 @@ // ========================================================================== using MongoDB.Bson.Serialization.Attributes; +using Squidex.Infrastructure; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class MongoFieldTests { - public class Entity + private class Entity { public string Id { get; set; } @@ -21,7 +22,7 @@ public class Entity public string Custom { get; set; } } - public sealed class Inherited : Entity + private sealed class Inherited : Entity { } diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoQueryTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoQueryTests.cs similarity index 97% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoQueryTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoQueryTests.cs index d46ae32af2..0dd5c25c85 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/MongoQueryTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/MongoQueryTests.cs @@ -9,17 +9,16 @@ using MongoDB.Driver; using NodaTime; using NodaTime.Text; -using Squidex.Infrastructure.MongoDb.Queries; +using Squidex.Infrastructure; using Squidex.Infrastructure.Queries; -using Squidex.Infrastructure.TestHelpers; +using Squidex.TestHelpers; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter; -using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class MongoQueryTests { - public class TestEntity + public sealed class TestEntity { public DomainId Id { get; set; } @@ -34,7 +33,7 @@ public class TestEntity static MongoQueryTests() { - TestUtils.SetupBson(); + MongoTestUtils.SetupBson(); } [Fact] diff --git a/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/NamedIdTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/NamedIdTests.cs new file mode 100644 index 0000000000..574d5cf09d --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/NamedIdTests.cs @@ -0,0 +1,94 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure; +using Squidex.TestHelpers; + +namespace Squidex.MongoDb.Infrastructure; + +public class NamedIdTests +{ + [Fact] + public void Should_serialize_and_deserialize_null_guid_token() + { + NamedId? value = null; + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_valid_guid_token() + { + var value = NamedId.Of(Guid.NewGuid(), "my-name"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_null_long_token() + { + NamedId? value = null; + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_valid_long_token() + { + var value = NamedId.Of(123L, "my-name"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_null_string_token() + { + NamedId? value = null; + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_valid_string_token() + { + var value = NamedId.Of(Guid.NewGuid().ToString(), "my-name"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_null_id_token() + { + NamedId? value = null; + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_valid_id_token() + { + var value = NamedId.Of(DomainId.NewGuid().ToString(), "my-name"); + + var serialized = value.SerializeAndDeserializeBson(); + + Assert.Equal(value, serialized); + } +} diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/TypeConverterStringSerializerTests.cs similarity index 95% rename from backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs rename to backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/TypeConverterStringSerializerTests.cs index 15df83652b..777c272894 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/TypeConverterStringSerializerTests.cs +++ b/backend/tests/Squidex.Data.Tests/MongoDb/Infrastructure/TypeConverterStringSerializerTests.cs @@ -7,8 +7,9 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; +using Squidex.Infrastructure; -namespace Squidex.Infrastructure.MongoDb; +namespace Squidex.MongoDb.Infrastructure; public class TypeConverterStringSerializerTests { @@ -17,7 +18,7 @@ private sealed record ValueHolder public T Value { get; set; } } - public TypeConverterStringSerializerTests() + static TypeConverterStringSerializerTests() { BsonStringSerializer.Register(); BsonStringSerializer.Register(); diff --git a/backend/tests/Squidex.Data.Tests/Squidex.Data.Tests.csproj b/backend/tests/Squidex.Data.Tests/Squidex.Data.Tests.csproj new file mode 100644 index 0000000000..368256d816 --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/Squidex.Data.Tests.csproj @@ -0,0 +1,48 @@ + + + Exe + net8.0 + Squidex + latest + enable + enable + en + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + \ No newline at end of file diff --git a/backend/tests/Squidex.Data.Tests/TestHelpers/Entities.cs b/backend/tests/Squidex.Data.Tests/TestHelpers/Entities.cs new file mode 100644 index 0000000000..9cf60f57d9 --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/TestHelpers/Entities.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +#pragma warning disable MA0048 // File name must match type name + +namespace Squidex.TestHelpers; + +public sealed class BsonAsDateTimeEntity +{ + [BsonRepresentation(BsonType.DateTime)] + public T Value { get; set; } +} + +public sealed class BsonAsInt64Entity +{ + [BsonRepresentation(BsonType.Int64)] + public T Value { get; set; } +} + +public sealed class BsonAsStringEntity +{ + [BsonRepresentation(BsonType.String)] + public T Value { get; set; } +} + +public sealed class BsonAsDefaultEntity +{ + public T Value { get; set; } +} diff --git a/backend/tests/Squidex.Data.Tests/TestHelpers/MongoTestUtils.cs b/backend/tests/Squidex.Data.Tests/TestHelpers/MongoTestUtils.cs new file mode 100644 index 0000000000..2656495b5d --- /dev/null +++ b/backend/tests/Squidex.Data.Tests/TestHelpers/MongoTestUtils.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.TestHelpers; + +namespace Squidex.TestHelpers; + +public static class MongoTestUtils +{ + public sealed class ObjectHolder + { + [BsonRequired] + public T Value1 { get; set; } + + [BsonRequired] + public T Value2 { get; set; } + } + + static MongoTestUtils() + { + SetupBson(); + } + + public static void SetupBson() + { + BsonDefaultConventions.Register(); + BsonDomainIdSerializer.Register(); + BsonEscapedDictionarySerializer.Register(); + BsonInstantSerializer.Register(); + BsonJsonConvention.Register(TestUtils.DefaultOptions()); + BsonJsonValueSerializer.Register(); + BsonStringSerializer.Register(); + } + + public static T SerializeAndDeserializeBson(this T value) + { + return SerializeAndDeserializeBson(value); + } + + public static TOut SerializeAndDeserializeBson(this TIn value) + { + using var stream = new MemoryStream(); + + using (var writer = new BsonBinaryWriter(stream)) + { + BsonSerializer.Serialize(writer, new ObjectHolder { Value1 = value, Value2 = value }); + } + + stream.Position = 0; + + using (var reader = new BsonBinaryReader(stream)) + { + return BsonSerializer.Deserialize>(reader).Value1; + } + } +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs index 429319a9c1..bdec4ecd8a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs @@ -27,7 +27,7 @@ public void Should_serialize_and_deserialize() clients = clients.Update("1", allowAnonymous: true, apiCallsLimit: 3); clients = clients.Revoke("4"); - var serialized = clients.SerializeAndDeserialize(); + var serialized = clients.SerializeAndDeserializeAsJson(); Assert.Equal(clients, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs index 8c2ab5134f..c324c53c59 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs @@ -21,7 +21,7 @@ public void Should_serialize_and_deserialize() contributors = contributors.Assign("2", Role.Editor); contributors = contributors.Assign("3", Role.Owner); - var serialized = contributors.SerializeAndDeserialize(); + var serialized = contributors.SerializeAndDeserializeAsJson(); Assert.Equal(contributors, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppImageTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppImageTests.cs index 03e684b937..ec7453041e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppImageTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppImageTests.cs @@ -37,7 +37,7 @@ public void Should_serialize_and_deserialize() { var appImage = new AppImage("image/png"); - var serialized = appImage.SerializeAndDeserialize(); + var serialized = appImage.SerializeAndDeserializeAsJson(); Assert.Equal(appImage, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs index ceab86e889..503a15b4ae 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs @@ -17,7 +17,7 @@ public void Should_serialize_and_deserialize() { var plan = new AssignedPlan(RefToken.Client("Me"), "free"); - var serialized = plan.SerializeAndDeserialize(); + var serialized = plan.SerializeAndDeserializeAsJson(); Assert.Equal(plan, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs index 069dd3bbc3..49b388867a 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppTests.cs @@ -230,7 +230,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Apps/App.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs index 6ebc53b95e..61d5d003fa 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs @@ -23,7 +23,7 @@ public void Should_serialize_and_deserialize() .Set(Language.DE, true, Language.IT) .MakeMaster(Language.FR); - var serialized = languages.SerializeAndDeserialize(); + var serialized = languages.SerializeAndDeserializeAsJson(); serialized.Should().BeEquivalentTo(languages); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs index 0677d62874..a6b1d3b021 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs @@ -34,7 +34,7 @@ public void Should_deserialize_from_old_role_format() "Permission1", "Permission2")); - var roles = source.SerializeAndDeserialize>(); + var roles = source.SerializeAndDeserializeAsJson>(); roles.Should().BeEquivalentTo(expected); } @@ -53,7 +53,7 @@ public void Should_serialize_and_deserialize() .Add("Property1", true) .Add("Property2", true)); - var roles = sut.SerializeAndDeserialize(); + var roles = sut.SerializeAndDeserializeAsJson(); roles.Should().BeEquivalentTo(sut); } @@ -63,7 +63,7 @@ public void Should_serialize_and_deserialize_empty() { var sut = Roles.Empty; - var roles = sut.SerializeAndDeserialize(); + var roles = sut.SerializeAndDeserializeAsJson(); Assert.Same(Roles.Empty, roles); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs index 3c06a70deb..d3e39c4421 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetFolderTests.cs @@ -66,7 +66,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Assets/AssetFolder.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs index 23271719fb..498054fa79 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Assets/AssetTests.cs @@ -175,7 +175,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Assets/Asset.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs index 3009782995..3dab4f7ba5 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentFieldDataTests.cs @@ -20,7 +20,7 @@ public void Should_serialize_and_deserialize() new ContentFieldData() .AddInvariant(12); - var serialized = fieldData.SerializeAndDeserialize(); + var serialized = fieldData.SerializeAndDeserializeAsJson(); Assert.Equal(fieldData, serialized); } @@ -32,7 +32,7 @@ public void Should_intern_invariant_key() new ContentFieldData() .AddInvariant(12); - var serialized = fieldData.SerializeAndDeserialize(); + var serialized = fieldData.SerializeAndDeserializeAsJson(); Assert.NotNull(string.IsInterned(serialized.Keys.First())); } @@ -44,7 +44,7 @@ public void Should_intern_known_language() new ContentFieldData() .AddLocalized("en", 12); - var serialized = fieldData.SerializeAndDeserialize(); + var serialized = fieldData.SerializeAndDeserializeAsJson(); Assert.NotNull(string.IsInterned(serialized.Keys.First())); } @@ -56,7 +56,7 @@ public void Should_not_intern_unknown_key() new ContentFieldData() .AddLocalized(Guid.NewGuid().ToString(), 12); - var serialized = fieldData.SerializeAndDeserialize(); + var serialized = fieldData.SerializeAndDeserializeAsJson(); Assert.Null(string.IsInterned(serialized.Keys.First())); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs index 31b41f37d0..e95d076c94 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentTests.cs @@ -27,7 +27,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Contents/Content.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs index 42e56c4a2b..108a5f34f4 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/StatusTests.cs @@ -104,7 +104,7 @@ public void Should_serialize_and_deserialize() { var status = Status.Draft; - var serialized = status.SerializeAndDeserialize(); + var serialized = status.SerializeAndDeserializeAsJson(); Assert.Equal(status, serialized); } @@ -117,7 +117,7 @@ public void Should_serialize_and_deserialize_as_dictionary_key() [Status.Draft] = 123 }; - var serialized = dictionary.SerializeAndDeserialize(); + var serialized = dictionary.SerializeAndDeserializeAsJson(); Assert.Equal(123, serialized[Status.Draft]); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs index 4bb2b4351e..10ee0799cf 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowJsonTests.cs @@ -33,7 +33,7 @@ public void Should_serialize_and_deserialize() }.ToReadonlyDictionary(), ReadonlyList.Create(DomainId.NewGuid()), "MyName"); - var serialized = workflow.SerializeAndDeserialize(); + var serialized = workflow.SerializeAndDeserializeAsJson(); Assert.Equal(workflow, serialized); } @@ -43,7 +43,7 @@ public void Should_serialize_and_deserialize_default() { var workflow = Workflow.Default; - var serialized = workflow.SerializeAndDeserialize(); + var serialized = workflow.SerializeAndDeserializeAsJson(); Assert.Equal(workflow, serialized); } @@ -53,7 +53,7 @@ public void Should_deserialize_old_noUpdate_condition() { var jsonStep = new { noUpdate = true }; - var serialized = jsonStep.SerializeAndDeserialize(); + var serialized = jsonStep.SerializeAndDeserializeAsJson(); Assert.Equal(new WorkflowStep(null, null, NoUpdate.Always), serialized); } @@ -63,7 +63,7 @@ public void Should_serialize_and_deserialize_no_update_condition() { var step = new WorkflowStep(NoUpdate: NoUpdate.When("Expression", "Role1", "Role2")); - var serialized = step.SerializeAndDeserialize(); + var serialized = step.SerializeAndDeserializeAsJson(); Assert.Equal(step, serialized); } @@ -73,7 +73,7 @@ public void Should_verify_roles_mapping_in_workflow_transition() { var source = new WorkflowTransitionSurrogate { Expression = "expression_1", Role = "role_1" }; - var serialized = source.SerializeAndDeserialize(); + var serialized = source.SerializeAndDeserializeAsJson(); var actual = serialized.ToSource(); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs index 0d3a004a3a..fecac991e5 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WorkflowsJsonTests.cs @@ -18,7 +18,7 @@ public void Should_serialize_and_deserialize() { var workflows = Workflows.Empty.Add(DomainId.NewGuid(), "my-workflow"); - var serialized = workflows.SerializeAndDeserialize(); + var serialized = workflows.SerializeAndDeserializeAsJson(); Assert.Equal(workflows, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs index 2988895694..12c9be29af 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/WriteContentTests.cs @@ -99,7 +99,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Contents/WriteContent.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs index fce7e88a33..8dca9852fe 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs @@ -180,7 +180,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Rules/Rule.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } @@ -190,7 +190,7 @@ public void Should_serialize_and_deserialize_and_migrate_trigger() { var rule_X = new Rule { Trigger = new MigratedTrigger(), Action = new TestAction1() }; - var serialized = rule_X.SerializeAndDeserialize(); + var serialized = rule_X.SerializeAndDeserializeAsJson(); Assert.IsType(serialized.Trigger); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index c1b5e3044e..ed5505fe56 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -525,7 +525,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Schemas/Schema.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs index e49c95d79d..558cf0ba34 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Model/Teams/TeamTests.cs @@ -95,7 +95,7 @@ public void Should_serialize_deserialize_state() { var json = File.ReadAllText("Model/Teams/Team.json").CleanJson(); - var serialized = TestUtils.SerializeWithoutNulls(TestUtils.DefaultSerializer.Deserialize(json)); + var serialized = TestUtils.SerializeWithoutNullsAsJson(TestUtils.DefaultSerializer.Deserialize(json)); Assert.Equal(json, serialized); } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/SubscriptionPublisherTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/SubscriptionPublisherTests.cs index bc4cb589d6..7f6de0b374 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/SubscriptionPublisherTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Subscriptions/SubscriptionPublisherTests.cs @@ -8,6 +8,7 @@ using Squidex.Domain.Apps.Core.Subscriptions; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Messaging.Subscriptions; diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj b/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj index 1520497b70..0d66a5eccc 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj @@ -10,12 +10,11 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs index 89a94c445a..54c949388e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs @@ -6,14 +6,10 @@ // ========================================================================== using System.Reflection; -using System.Runtime.Serialization.Formatters.Binary; using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Attributes; using NetTopologySuite.IO.Converters; using NodaTime; using NodaTime.Serialization.SystemTextJson; @@ -27,13 +23,13 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; +using Squidex.Events.Utils; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.System; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.Json; using Squidex.Infrastructure.Reflection; @@ -48,34 +44,13 @@ public static class TestUtils public static readonly IJsonSerializer DefaultSerializer = CreateSerializer(); - public sealed class ObjectHolder + private sealed class ObjectHolder { - [BsonRequired] public T Value1 { get; set; } - [BsonRequired] public T Value2 { get; set; } } - static TestUtils() - { - SetupBson(); - } - - public static void SetupBson() - { - BsonDefaultConventions.Register(); - BsonDomainIdSerializer.Register(); - BsonEscapedDictionarySerializer.Register(); - BsonEscapedDictionarySerializer.Register(); - BsonEscapedDictionarySerializer.Register(); - BsonInstantSerializer.Register(); - BsonJsonConvention.Register(DefaultOptions()); - BsonJsonValueSerializer.Register(); - BsonStringSerializer.Register(); - BsonStringSerializer.Register(); - } - public static TypeRegistry CreateTypeRegistry(Assembly assembly) { var typeRegistry = @@ -106,13 +81,23 @@ public static JsonSerializerOptions DefaultOptions(Action options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); options.Converters.Add(new GeoJsonConverterFactory()); - options.Converters.Add(new PolymorphicConverter(TypeRegistry)); + options.Converters.Add(new HeaderValueConverter()); + options.Converters.Add(new JsonValueConverter()); options.Converters.Add(new PolymorphicConverter(TypeRegistry)); + options.Converters.Add(new PolymorphicConverter(TypeRegistry)); options.Converters.Add(new PolymorphicConverter(TypeRegistry)); options.Converters.Add(new PolymorphicConverter(TypeRegistry)); - options.Converters.Add(new JsonValueConverter()); options.Converters.Add(new ReadonlyDictionaryConverterFactory()); options.Converters.Add(new ReadonlyListConverterFactory()); + options.Converters.Add(new StringConverter()); + options.Converters.Add(new StringConverter()); + options.Converters.Add(new StringConverter()); + options.Converters.Add(new StringConverter>()); + options.Converters.Add(new StringConverter>()); + options.Converters.Add(new StringConverter>()); + options.Converters.Add(new StringConverter>()); + options.Converters.Add(new StringConverter()); + options.Converters.Add(new StringConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter, FieldsSurrogate>()); options.Converters.Add(new SurrogateJsonConverter, JsonFilterSurrogate>()); @@ -123,15 +108,6 @@ public static JsonSerializerOptions DefaultOptions(Action options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); options.Converters.Add(new SurrogateJsonConverter()); - options.Converters.Add(new StringConverter()); - options.Converters.Add(new StringConverter()); - options.Converters.Add(new StringConverter>()); - options.Converters.Add(new StringConverter>()); - options.Converters.Add(new StringConverter>()); - options.Converters.Add(new StringConverter>()); - options.Converters.Add(new StringConverter()); - options.Converters.Add(new StringConverter()); - options.Converters.Add(new StringConverter()); options.Converters.Add(new JsonStringEnumConverter()); options.IncludeFields = true; options.TypeInfoResolver = new DefaultJsonTypeInfoResolver() @@ -143,62 +119,26 @@ public static JsonSerializerOptions DefaultOptions(Action return options; } - public static T SerializeAndDeserializeBinary(this T source) - { - using (var stream = new MemoryStream()) - { - var formatter = new BinaryFormatter(); - - formatter.Serialize(stream, source!); - - stream.Position = 0; - - return (T)formatter.Deserialize(stream); - } - } - - public static T SerializeAndDeserializeBson(this T value) - { - return SerializeAndDeserializeBson(value); - } - - public static TOut SerializeAndDeserializeBson(this TIn value) - { - using var stream = new MemoryStream(); - - using (var writer = new BsonBinaryWriter(stream)) - { - BsonSerializer.Serialize(writer, new ObjectHolder { Value1 = value, Value2 = value }); - } - - stream.Position = 0; - - using (var reader = new BsonBinaryReader(stream)) - { - return BsonSerializer.Deserialize>(reader).Value1; - } - } - - public static T SerializeAndDeserialize(this T value) + public static T SerializeAndDeserializeAsJson(this T value) { - return SerializeAndDeserialize(value); + return SerializeAndDeserializeAsJson(value); } - public static TOut SerializeAndDeserialize(this TIn value) + public static TOut SerializeAndDeserializeAsJson(this TIn value) { var json = DefaultSerializer.Serialize(new ObjectHolder { Value1 = value, Value2 = value }); return DefaultSerializer.Deserialize>(json).Value1; } - public static T Deserialize(string value) + public static T DeserializeJson(string value) { var json = DefaultSerializer.Serialize(new ObjectHolder { Value1 = value, Value2 = value }); return DefaultSerializer.Deserialize>(json).Value1; } - public static string SerializeWithoutNulls(this T value) + public static string SerializeWithoutNullsAsJson(this T value) { var options = DefaultOptions(options => { diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs index 92117dd2e8..221e2a4703 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppEventDeleterTests.cs @@ -6,7 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Entities.TestHelpers; -using Squidex.Infrastructure.EventSourcing; +using Squidex.Events; namespace Squidex.Domain.Apps.Entities.Apps; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs index 3952db043e..2a692198b9 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppPermanentDeleterTests.cs @@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.Apps.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Apps; +using Squidex.Events; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs index 4ce6a19f99..4ac8602c92 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetPermanentDeleterTests.cs @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs index d5504986a6..95e4eb094c 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetUsageTrackerTests.cs @@ -9,6 +9,7 @@ using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.States; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs index 08acb029ee..228a9761ce 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RecursiveDeleterTests.cs @@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs index c39e74bba4..0da40c77ac 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/RepairFilesTests.cs @@ -8,6 +8,7 @@ using Squidex.Assets; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs index 85f8500ee6..564a4cf861 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs @@ -7,10 +7,10 @@ using System.Globalization; using Squidex.Domain.Apps.Core.TestHelpers; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; @@ -181,8 +181,8 @@ DomainId RandomDomainId() var envelope = Envelope.Create(@event); - envelope.Headers.Add("Id", JsonValue.Create(@event.Id)); - envelope.Headers.Add("Index", JsonValue.Create(i)); + envelope.Headers.Add("Id", @event.Id.ToString()); + envelope.Headers.Add("Index", i); sourceEvents.Add(($"My-{RandomDomainId()}", envelope)); } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentCollaborationHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentCollaborationHandlerTests.cs index 3129a0c9c6..0d3ef4b076 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentCollaborationHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Collaboration/CommentCollaborationHandlerTests.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Comments; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Shared.Users; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs index fa160e3e88..8db1c172ef 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEventDeleterTests.cs @@ -7,8 +7,8 @@ using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.TestHelpers; +using Squidex.Events; using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Contents; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index efe82fab23..85047695d4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -118,6 +118,7 @@ protected CachingGraphQLResolver CreateSut(params Schema[] schemas) var serviceProvider = new ServiceCollection() + .AddSingleton(TimeProvider.System) .AddLogging(options => { options.AddDebug(); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs deleted file mode 100644 index 0be8432b9a..0000000000 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/StatusSerializerTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Core.TestHelpers; - -namespace Squidex.Domain.Apps.Entities.Contents.MongoDb; - -public sealed class StatusSerializerTests -{ - private sealed record ValueHolder - { - public T Value { get; set; } - } - - public StatusSerializerTests() - { - TestUtils.SetupBson(); - } - - [Fact] - public void Should_serialize_and_deserialize_status() - { - var source = new ValueHolder - { - Value = Status.Published - }; - - var deserialized = SerializeAndDeserializeBson(source); - - Assert.Equal(source, deserialized); - } - - [Fact] - public void Should_serialize_and_deserialize_default_status() - { - var source = new ValueHolder - { - Value = default - }; - - var deserialized = SerializeAndDeserializeBson(source); - - Assert.Equal(source, deserialized); - } - - private static T SerializeAndDeserializeBson(T value) - { - var stream = new MemoryStream(); - - using (var writer = new BsonBinaryWriter(stream)) - { - BsonSerializer.Serialize(writer, value); - - writer.Flush(); - } - - stream.Position = 0; - - using (var reader = new BsonBinaryReader(stream)) - { - var actual = BsonSerializer.Deserialize(reader); - - return actual; - } - } -} diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AzureTextIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AzureTextIndexTests.cs index 41128ff49c..45407da2f6 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AzureTextIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/AzureTextIndexTests.cs @@ -18,9 +18,9 @@ public class AzureTextIndexTests(AzureTextIndexFixture fixture) : TextIndexerTes public AzureTextIndexFixture _ { get; } = fixture; - public override ITextIndex CreateIndex() + public override Task CreateSutAsync() { - return _.Index; + return Task.FromResult(_.Index); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexFixture.cs index 8570c37f8c..961de0d92b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexFixture.cs @@ -8,24 +8,30 @@ using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Extensions.Text.ElasticSearch; +using Testcontainers.Elasticsearch; namespace Squidex.Domain.Apps.Entities.Contents.Text; public sealed class ElasticSearchTextIndexFixture : IAsyncLifetime { - public ElasticSearchTextIndex Index { get; } + private readonly ElasticsearchContainer elastic = + new ElasticsearchBuilder() + .WithReuse(true) + .WithLabel("resuse-id", "elastic-text") + .Build(); - public ElasticSearchTextIndexFixture() + public ElasticSearchTextIndex Index { get; private set; } + + public async Task InitializeAsync() { + await elastic.StopAsync(); + Index = new ElasticSearchTextIndex( - new ElasticSearchClient(TestConfig.Configuration["elastic:configuration"]!), + new ElasticSearchClient(elastic.GetConnectionString()), TestConfig.Configuration["elastic:indexName"]!, TestUtils.DefaultSerializer); - } - public Task InitializeAsync() - { - return Index.InitializeAsync(default); + await Index.InitializeAsync(default); } public Task DisposeAsync() diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexTests.cs index 318d963bad..82ef3781e0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/ElasticSearchTextIndexTests.cs @@ -18,9 +18,9 @@ public class ElasticSearchTextIndexTests(ElasticSearchTextIndexFixture fixture) public ElasticSearchTextIndexFixture _ { get; } = fixture; - public override ITextIndex CreateIndex() + public override Task CreateSutAsync() { - return _.Index; + return Task.FromResult(_.Index); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/OpenSearchTextIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/OpenSearchTextIndexTests.cs index c6c2871811..70f800e1d3 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/OpenSearchTextIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/OpenSearchTextIndexTests.cs @@ -18,9 +18,9 @@ public class OpenSearchTextIndexTests(OpenSearchTextIndexFixture fixture) : Text public OpenSearchTextIndexFixture _ { get; } = fixture; - public override ITextIndex CreateIndex() + public override Task CreateSutAsync() { - return _.Index; + return Task.FromResult(_.Index); } [Fact] diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerTestsBase.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerTestsBase.cs index f27b2ac8e8..b3628a6f80 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerTestsBase.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerTestsBase.cs @@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.Contents.Text.State; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Contents; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json.Objects; @@ -20,14 +21,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text; public abstract class TextIndexerTestsBase : GivenContext { + private TextIndexingProcess? process; + protected readonly List ids1 = [DomainId.NewGuid()]; protected readonly List ids2 = [DomainId.NewGuid()]; - private readonly Lazy sut; - - protected TextIndexingProcess Sut - { - get { return sut.Value; } - } public virtual bool SupportsQuerySyntax => true; @@ -35,24 +32,14 @@ protected TextIndexingProcess Sut public virtual int WaitAfterUpdate => 0; - protected TextIndexerTestsBase() - { - sut = new Lazy(CreateSut); - } - - private TextIndexingProcess CreateSut() - { - var index = CreateIndex(); - - return new TextIndexingProcess(TestUtils.DefaultSerializer, index, new InMemoryTextIndexerState()); - } - - public abstract ITextIndex CreateIndex(); + public abstract Task CreateSutAsync(); [Fact] - public void Should_return_content_filter_for_events_filter() + public async Task Should_return_content_filter_for_events_filter() { - Assert.Equal(StreamFilter.Prefix("content-"), Sut.EventsFilter); + var sut = await GetProcessAsync(); + + Assert.Equal(StreamFilter.Prefix("content-"), sut.EventsFilter); } [Fact] @@ -410,11 +397,13 @@ protected Task DeleteAsync(DomainId id) private async Task UpdateAsync(DomainId id, ContentEvent contentEvent) { + var sut = await GetProcessAsync(); + contentEvent.ContentId = id; contentEvent.AppId = AppId; contentEvent.SchemaId = SchemaId; - await Sut.On(Enumerable.Repeat(Envelope.Create(contentEvent), 1)); + await sut.On(Enumerable.Repeat(Envelope.Create(contentEvent), 1)); await Task.Delay(WaitAfterUpdate, default); } @@ -450,10 +439,14 @@ private static ContentData GeoJsonData(string field, double latitude, double lon protected async Task SearchGeo(List? expected, string field, double latitude, double longitude, SearchScope target = SearchScope.All) { - var query = new GeoQuery(SchemaId.Id, field, latitude, longitude, 1000, 1000); + var query = new GeoQuery(SchemaId.Id, field, latitude, longitude, 1000, 1000) + { + SchemaId = default + }; - var actual = await Sut.TextIndex.SearchAsync(App, query, target, default); + var sut = await GetProcessAsync(); + var actual = await sut.TextIndex.SearchAsync(App, query, target, default); if (expected != null) { actual.Should().BeEquivalentTo(expected.ToHashSet()); @@ -468,11 +461,12 @@ protected async Task SearchText(List? expected, string text, SearchSco { var query = new TextQuery(text, 1000) { - RequiredSchemaIds = new List { SchemaId.Id } + RequiredSchemaIds = [SchemaId.Id] }; - var actual = await Sut.TextIndex.SearchAsync(App, query, target, default); + var sut = await GetProcessAsync(); + var actual = await sut.TextIndex.SearchAsync(App, query, target, default); if (expected != null) { actual.Should().BeEquivalentTo(expected.ToHashSet()); @@ -490,8 +484,9 @@ protected async Task SearchByAppText(List? expected, string text, Sear PreferredSchemaId = Schema.Id }; - var actual = await Sut.TextIndex.SearchAsync(App, query, target, default); + var sut = await GetProcessAsync(); + var actual = await sut.TextIndex.SearchAsync(App, query, target, default); if (expected != null) { actual.Should().BeEquivalentTo(expected.ToHashSet()); @@ -501,4 +496,16 @@ protected async Task SearchByAppText(List? expected, string text, Sear actual.Should().BeEmpty(); } } + + private async Task GetProcessAsync() + { + if (process == null) + { + var index = await CreateSutAsync(); + + process = new TextIndexingProcess(TestUtils.DefaultSerializer, index, new InMemoryTextIndexerState()); + } + + return process; + } } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs index 12c2c55332..bfff642c76 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Invitation/InvitationEventConsumerTests.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Teams; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Shared.Users; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs index 26f295e94b..c61ce12514 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaPermanentDeleterTests.cs @@ -10,6 +10,7 @@ using Squidex.Domain.Apps.Entities.Schemas.DomainObject; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index 46286206af..fd9e0e3e7d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -13,9 +13,7 @@ - - @@ -23,7 +21,7 @@ - + @@ -35,6 +33,7 @@ + diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/VerifySettings.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/VerifySettings.cs index 31b95e48b5..ebf57fb010 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/VerifySettings.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/VerifySettings.cs @@ -9,6 +9,7 @@ using Argon; using NodaTime; using NodaTime.Text; +using Squidex.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; @@ -47,6 +48,28 @@ public override void Write(VerifyJsonWriter writer, JsonObject value) } } + private sealed class HeaderValueConverter : WriteOnlyJsonConverter + { + public override void Write(VerifyJsonWriter writer, HeaderValue value) + { + switch (value.Value) + { + case string s: + writer.WriteValue(s); + break; + case double n: + writer.WriteValue(n); + break; + case bool b: + writer.WriteValue(b); + break; + case null: + writer.WriteNull(); + break; + } + } + } + private sealed class JsonValueConverter : WriteOnlyJsonConverter { public override void Write(VerifyJsonWriter writer, JsonValue value) @@ -128,15 +151,22 @@ public static void Initialize() VerifierSettings.AddExtraSettings(s => { + s.Converters.Add(new HeaderValueConverter()); s.Converters.Add(new JsonArrayConverter()); s.Converters.Add(new JsonObjectConverter()); s.Converters.Add(new JsonValueConverter()); s.ContractResolver = new ContractResolver(s.ContractResolver!); }); + static bool IsDateTime(object? value) + { + return value is string s && InstantPattern.ExtendedIso.Parse(s).Success; + } + VerifierSettings.ScrubInlineGuids(); VerifierSettings.IgnoreMembersWithType(); - VerifierSettings.IgnoreInstance(x => x.Type == JsonValueType.String && InstantPattern.ExtendedIso.Parse(x.ToString()).Success); + VerifierSettings.IgnoreInstance(x => IsDateTime(x.Value)); + VerifierSettings.IgnoreInstance(x => IsDateTime(x.Value)); VerifierSettings.IgnoreMember("Secret"); } } diff --git a/backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj b/backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj index 5c23cb9968..9aec53626c 100644 --- a/backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj +++ b/backend/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj @@ -8,14 +8,13 @@ enable - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyDictionaryTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyDictionaryTests.cs index 7a1d75fe70..a09dd08e5e 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyDictionaryTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyDictionaryTests.cs @@ -95,7 +95,7 @@ public void Should_serialize_and_deserialize() [13] = 3 }.ToReadonlyDictionary(); - var serialized = sut.SerializeAndDeserialize(); + var serialized = sut.SerializeAndDeserializeJson(); Assert.Equal(sut, serialized); } @@ -110,7 +110,7 @@ public void Should_serialize_and_deserialize_inherited() [13] = 3 }); - var serialized = sut.SerializeAndDeserialize(); + var serialized = sut.SerializeAndDeserializeJson(); Assert.Equal(sut, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyListTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyListTests.cs index 36b56f81bc..92836573c5 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyListTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Collections/ReadonlyListTests.cs @@ -71,7 +71,7 @@ public void Should_serialize_and_deserialize() 3 }.ToReadonlyList(); - var serialized = sut.SerializeAndDeserialize(); + var serialized = sut.SerializeAndDeserializeJson(); Assert.Equal(sut, serialized); } @@ -86,7 +86,7 @@ public void Should_serialize_and_deserialize_inherited() 3 }); - var serialized = sut.SerializeAndDeserialize(); + var serialized = sut.SerializeAndDeserializeJson(); Assert.Equal(sut, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandResultTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandResultTests.cs index 42c6f1305a..370ac48a3b 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandResultTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/CommandResultTests.cs @@ -16,7 +16,7 @@ public void Should_serialize_and_deserialize() { var sut = new CommandResult(DomainId.NewGuid(), 3, 2, null!); - var serialized = sut.SerializeAndDeserialize(); + var serialized = sut.SerializeAndDeserializeJson(); Assert.Equal(sut, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/DomainIdTests.cs b/backend/tests/Squidex.Infrastructure.Tests/DomainIdTests.cs index b1ba0a94ae..562e60ba8e 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/DomainIdTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/DomainIdTests.cs @@ -158,7 +158,7 @@ public void Should_serialize_and_deserialize() { var domainId = DomainId.Create("123"); - var serialized = domainId.SerializeAndDeserialize(); + var serialized = domainId.SerializeAndDeserializeJson(); Assert.Equal(domainId, serialized); } @@ -173,7 +173,7 @@ public void Should_serialize_and_deserialize_in_object() Id2 = NamedId.Of(DomainId.NewGuid(), "2") }; - var serialized = obj.SerializeAndDeserialize(); + var serialized = obj.SerializeAndDeserializeJson(); serialized.Should().BeEquivalentTo(obj); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs index 10047b8025..a0a36476d9 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerProcessorTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Microsoft.Extensions.Logging; +using Squidex.Events; using Squidex.Infrastructure.States; using Squidex.Infrastructure.TestHelpers; @@ -58,7 +59,7 @@ public EventConsumerProcessorTests() } }; - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .Returns(eventSubscription); A.CallTo(() => eventConsumer.Name) @@ -127,7 +128,7 @@ public async Task Should_not_subscribe_to_event_store_if_stopped_in_db() AssertGrainState(isStopped: true, position: initialPosition); - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .MustNotHaveHappened(); } @@ -141,7 +142,7 @@ public async Task Should_subscribe_to_event_store_if_not_found_in_db() AssertGrainState(isStopped: false, position: initialPosition); - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .MustHaveHappenedOnceExactly(); } @@ -157,7 +158,7 @@ public async Task Should_subscribe_to_event_store_if_failed() AssertGrainState(isStopped: false, position: initialPosition); - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .MustHaveHappenedOnceExactly(); } @@ -171,7 +172,7 @@ public async Task Should_subscribe_to_event_store_if_not_stopped_in_db() AssertGrainState(isStopped: false, position: initialPosition); - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .MustHaveHappenedOnceExactly(); } @@ -528,7 +529,7 @@ public async Task Should_start_after_stop_if_handling_failed() A.CallTo(() => eventSubscription.Dispose()) .MustHaveHappenedOnceExactly(); - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) + A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) .MustHaveHappened(2, Times.Exactly); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerStateTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerStateTests.cs index 0d6bf472b8..e632d20127 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerStateTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/Consume/EventConsumerStateTests.cs @@ -22,7 +22,7 @@ public void Should_serialize_and_deserialize() Position = "Position" }; - var serialized = state.SerializeAndDeserialize(); + var serialized = state.SerializeAndDeserializeJson(); serialized.Should().BeEquivalentTo(state); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventFormatterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventFormatterTests.cs index 4780d6f633..0977ffca81 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventFormatterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventFormatterTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using NodaTime; +using Squidex.Events; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.TestHelpers; diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs index 0a15a2a367..404367b5ce 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using NodaTime; +using Squidex.Events; namespace Squidex.Infrastructure.EventSourcing; @@ -24,7 +25,7 @@ public void Should_set_and_get_timestamp() sut.SetTimestamp(timestamp); - Assert.Equal(timestamp, sut.Headers.Timestamp()); + Assert.Equal(timestamp, sut.Headers.TimestampAsInstant()); Assert.Equal(timestamp, sut.Headers.GetInstant("Timestamp")); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs deleted file mode 100644 index dc482e0c34..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Infrastructure.Json.Objects; -using Squidex.Infrastructure.TestHelpers; - -namespace Squidex.Infrastructure.EventSourcing; - -public class EnvelopeHeadersTests -{ - [Fact] - public void Should_create_headers() - { - var headers = new EnvelopeHeaders(); - - Assert.Empty(headers); - } - - [Fact] - public void Should_create_headers_as_copy() - { - var source = new EnvelopeHeaders - { - ["key1"] = JsonValue.Create(13) - }; - - var headers = new EnvelopeHeaders(source); - - CompareHeaders(headers, source); - } - - [Fact] - public void Should_clone_headers() - { - var source = new EnvelopeHeaders - { - ["key1"] = JsonValue.Create(13) - }; - - var headers = source.CloneHeaders(); - - CompareHeaders(headers, source); - } - - [Fact] - public void Should_serialize_and_deserialize() - { - var source = new EnvelopeHeaders - { - ["key1"] = JsonValue.Create(13) - }; - - var deserialized = source.SerializeAndDeserialize(); - - CompareHeaders(deserialized, source); - } - - private static void CompareHeaders(EnvelopeHeaders lhs, EnvelopeHeaders rhs) - { - foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct()) - { - Assert.Equal(lhs[key].ToString(), rhs[key].ToString()); - } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs index 1f887fb95f..724c2f2884 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs @@ -21,7 +21,7 @@ public void Should_serialize_and_deserialize() { var value = Envelope.Create(new MyEvent { Value = 1 }); - var deserialized = value.SerializeAndDeserialize(); + var deserialized = value.SerializeAndDeserializeJson(); Assert.Equal(1, deserialized.Payload.Value); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs deleted file mode 100644 index a33c32cc6f..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/EventStoreTests.cs +++ /dev/null @@ -1,528 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; - -namespace Squidex.Infrastructure.EventSourcing; - -public abstract class EventStoreTests where T : IEventStore -{ - private readonly Lazy sut; - private string subscriptionPosition; - - public sealed class EventSubscriber : IEventSubscriber - { - public List LastEvents { get; } = []; - - public string LastPosition { get; set; } - - public void Dispose() - { - } - - public void WakeUp() - { - } - - public ValueTask OnErrorAsync(IEventSubscription subscription, Exception exception) - { - throw exception; - } - - public ValueTask OnNextAsync(IEventSubscription subscription, StoredEvent @event) - { - LastPosition = @event.EventPosition; - LastEvents.Add(@event); - return default; - } - } - - protected T Sut - { - get => sut.Value; - } - - protected EventStoreTests() - { -#pragma warning disable MA0056 // Do not call overridable members in constructor - sut = new Lazy(CreateStore); -#pragma warning restore MA0056 // Do not call overridable members in constructor - } - - public abstract T CreateStore(); - - [Fact] - public async Task Should_throw_exception_for_version_mismatch() - { - var streamName = $"test-{Guid.NewGuid()}"; - - var commit = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - await Assert.ThrowsAsync(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, commit)); - } - - [Fact] - public async Task Should_throw_exception_for_version_mismatch_and_update() - { - var streamName = $"test-{Guid.NewGuid()}"; - - var commit = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit); - - await Assert.ThrowsAsync(() => Sut.AppendAsync(Guid.NewGuid(), streamName, 0, commit)); - } - - [Fact] - public async Task Should_append_events() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName); - - var commit1 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - var commit2 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit1); - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit2); - - var readEvents1 = await QueryAsync(streamName); - var readEvents2 = await QueryAllAsync(streamFilter); - - var expected = new[] - { - new StoredEvent(streamName, "Position", 0, commit1[0]), - new StoredEvent(streamName, "Position", 1, commit1[1]), - new StoredEvent(streamName, "Position", 2, commit2[0]), - new StoredEvent(streamName, "Position", 3, commit2[1]) - }; - - ShouldBeEquivalentTo(readEvents1, expected); - ShouldBeEquivalentTo(readEvents2, expected); - } - - [Fact] - public async Task Should_append_events_unsafe() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName); - - var commit1 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - await Sut.AppendUnsafeAsync(new List - { - new EventCommit(Guid.NewGuid(), streamName, -1, commit1) - }); - - var readEvents1 = await QueryAsync(streamName); - var readEvents2 = await QueryAllAsync(streamFilter); - - var expected = new[] - { - new StoredEvent(streamName, "Position", 0, commit1[0]), - new StoredEvent(streamName, "Position", 1, commit1[1]) - }; - - ShouldBeEquivalentTo(readEvents1, expected); - ShouldBeEquivalentTo(readEvents2, expected); - } - - [Fact] - public async Task Should_subscribe_to_events() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName); - - var commit1 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - var readEvents = await QueryWithSubscriptionAsync(streamFilter, async () => - { - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit1); - }); - - var expected = new[] - { - new StoredEvent(streamName, "Position", 0, commit1[0]), - new StoredEvent(streamName, "Position", 1, commit1[1]) - }; - - ShouldBeEquivalentTo(readEvents, expected); - } - - [Fact] - public async Task Should_subscribe_to_next_events() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName); - - var commit1 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - // Append and read in parallel. - await QueryWithSubscriptionAsync(streamFilter, async () => - { - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit1); - }); - - var commit2 = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - // Append and read in parallel. - var readEventsFromPosition = await QueryWithSubscriptionAsync(streamFilter, async () => - { - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit2); - }); - - var expectedFromPosition = new[] - { - new StoredEvent(streamName, "Position", 2, commit2[0]), - new StoredEvent(streamName, "Position", 3, commit2[1]) - }; - - var readEventsFromBeginning = await QueryWithSubscriptionAsync(streamFilter, fromBeginning: true); - - var expectedFromBeginning = new[] - { - new StoredEvent(streamName, "Position", 0, commit1[0]), - new StoredEvent(streamName, "Position", 1, commit1[1]), - new StoredEvent(streamName, "Position", 2, commit2[0]), - new StoredEvent(streamName, "Position", 3, commit2[1]) - }; - - ShouldBeEquivalentTo(readEventsFromPosition?.TakeLast(2), expectedFromPosition); - ShouldBeEquivalentTo(readEventsFromBeginning?.TakeLast(4), expectedFromBeginning); - } - - [Fact] - public async Task Should_subscribe_with_parallel_writes() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Prefix(streamName); - - var numTasks = 50; - var numEvents = 100; - - // Append and read in parallel. - var readEvents = await QueryWithSubscriptionAsync(streamFilter, async () => - { - await Parallel.ForEachAsync(Enumerable.Range(0, numTasks), async (i, ct) => - { - var fullStreamName = $"{streamName}-{Guid.NewGuid()}"; - - for (var j = 0; j < numEvents; j++) - { - var commit1 = new[] - { - CreateEventData(i * j) - }; - -#pragma warning disable MA0040 // Forward the CancellationToken parameter to methods that take one - await Sut.AppendAsync(Guid.NewGuid(), fullStreamName, EtagVersion.Any, commit1); -#pragma warning restore MA0040 // Forward the CancellationToken parameter to methods that take one - } - }); - }); - - Assert.Equal(numEvents * numTasks, readEvents?.Count); - } - - [Fact] - public async Task Should_read_multiple_streams() - { - var streamName1 = $"test-{Guid.NewGuid()}"; - var streamName2 = $"test-{Guid.NewGuid()}"; - - var stream1Commit = new[] - { - CreateEventData(1), - CreateEventData(2) - }; - - var stream2Commit = new[] - { - CreateEventData(3), - CreateEventData(4) - }; - - await Sut.AppendAsync(Guid.NewGuid(), streamName1, EtagVersion.Any, stream1Commit); - await Sut.AppendAsync(Guid.NewGuid(), streamName2, EtagVersion.Any, stream2Commit); - - var readEvents = await Sut.QueryAllAsync(StreamFilter.Name(streamName1, streamName2)).ToListAsync(); - - var expected1 = new[] - { - new StoredEvent(streamName1, "Position", 0, stream1Commit[0]), - new StoredEvent(streamName1, "Position", 1, stream1Commit[1]) - }; - - var expected2 = new[] - { - new StoredEvent(streamName2, "Position", 0, stream2Commit[0]), - new StoredEvent(streamName2, "Position", 1, stream2Commit[1]) - }; - - ShouldBeEquivalentTo(readEvents.Where(x => x.StreamName == streamName1), expected1); - ShouldBeEquivalentTo(readEvents.Where(x => x.StreamName == streamName2), expected2); - } - - [Theory] - [InlineData(1, 30)] - [InlineData(5, 30)] - [InlineData(5, 300)] - [InlineData(5, 3000)] - public async Task Should_query_events_from_offset(int commits, int count) - { - var streamName = $"test-{Guid.NewGuid()}"; - - var eventsWritten = await AppendEventsAsync(streamName, count, commits); - - var readEvents0 = await QueryAsync(streamName); - var readEvents1 = await QueryAsync(streamName, count - 2); - var readEvents2 = await QueryAllAsync(default, readEvents0[^2].EventPosition); - - var expected = new[] - { - new StoredEvent(streamName, "Position", count - 1, eventsWritten[^1]) - }; - - ShouldBeEquivalentTo(readEvents1, expected); - ShouldBeEquivalentTo(readEvents2, expected); - } - - [Theory] - [InlineData(5, 30)] - [InlineData(5, 300)] - [InlineData(5, 3000)] - public async Task Should_query_all_reverse_by_names(int commits, int count) - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName, "invalid"); - - var eventsWritten = await AppendEventsAsync(streamName, count, commits); - var eventsStored = eventsWritten.Select((x, i) => new StoredEvent(streamName, "Position", i, x)).ToArray(); - - for (var take = 0; take < count; take += count / 10) - { - var eventsExpected = eventsStored.Reverse().Take(take).ToArray(); - var eventsQueried = await Sut.QueryAllReverseAsync(streamFilter, default, take).ToArrayAsync(); - - ShouldBeEquivalentTo(eventsQueried, eventsExpected); - } - } - - [Theory] - [InlineData(5, 30)] - [InlineData(5, 300)] - [InlineData(5, 3000)] - public async Task Should_query_all_reverse_by_filter(int commits, int count) - { - var streamName = $"test-{Guid.NewGuid()}-suffix"; - var streamFilter = StreamFilter.Prefix(streamName[..^7], "invalid"); - - var eventsWritten = await AppendEventsAsync(streamName, count, commits); - var eventsStored = eventsWritten.Select((x, i) => new StoredEvent(streamName, "Position", i, x)).ToArray(); - - for (var take = 0; take < count; take += count / 10) - { - var eventsExpected = eventsStored.Reverse().Take(take).ToArray(); - var eventsQueried = await Sut.QueryAllReverseAsync(streamFilter, default, take).ToArrayAsync(); - - ShouldBeEquivalentTo(eventsQueried, eventsExpected); - } - } - - [Theory] - [InlineData(5, 30)] - [InlineData(5, 300)] - [InlineData(5, 3000)] - public async Task Should_read_all_reverse(int commits, int count) - { - var streamName = $"test-{Guid.NewGuid()}-suffix"; - var streamFilter = default(StreamFilter); - - await AppendEventsAsync(streamName, count, commits); - - for (var take = 0; take < count; take += count / 10) - { - var eventsQueried = await Sut.QueryAllReverseAsync(streamFilter, default, take).ToArrayAsync(); - - Assert.Equal(take, eventsQueried.Length); - } - } - - [Fact] - public async Task Should_delete_by_filter() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Prefix($"{streamName[..10]}"); - - await AppendEventsAsync(streamName, 2, 1); - - IReadOnlyList? readEvents = null; - - for (var i = 0; i < 5; i++) - { - await Sut.DeleteAsync(streamFilter); - - readEvents = await QueryAsync(streamName); - - if (readEvents.Count == 0) - { - break; - } - - // Get event store needs a little bit of time for the projections. - await Task.Delay(1000); - } - - Assert.Empty(readEvents!); - } - - [Fact] - public async Task Should_delete_by_name() - { - var streamName = $"test-{Guid.NewGuid()}"; - var streamFilter = StreamFilter.Name(streamName); - - await AppendEventsAsync(streamName, 2, 1); - - IReadOnlyList? readEvents = null; - - for (var i = 0; i < 5; i++) - { - await Sut.DeleteAsync(streamFilter); - - readEvents = await QueryAsync(streamName); - - if (readEvents.Count == 0) - { - break; - } - - // Get event store needs a little bit of time for the projections. - await Task.Delay(1000); - } - - Assert.Empty(readEvents!); - } - - private async Task> QueryAsync(string streamName, long position = EtagVersion.Any) - { - return await Sut.QueryStreamAsync(streamName, position); - } - - private async Task?> QueryAllAsync(StreamFilter filter, string? position = null) - { - return await Sut.QueryAllAsync(filter, position).ToListAsync(); - } - - private static EventData CreateEventData(int i) - { - var headers = new EnvelopeHeaders - { - [CommonHeaders.EventId] = Guid.NewGuid().ToString() - }; - - return new EventData($"Type{i}", headers, i.ToString(CultureInfo.InvariantCulture)); - } - - private async Task?> QueryWithSubscriptionAsync(StreamFilter streamFilter, - Func? subscriptionRunning = null, bool fromBeginning = false) - { - var subscriber = new EventSubscriber(); - - IEventSubscription? subscription = null; - try - { - subscription = Sut.CreateSubscription(subscriber, streamFilter, fromBeginning ? null : subscriptionPosition); - - if (subscriptionRunning != null) - { - await subscriptionRunning(); - } - - using (var cts = new CancellationTokenSource(30000)) - { - while (!cts.IsCancellationRequested) - { - subscription.WakeUp(); - - await Task.Delay(2000, cts.Token); - - if (subscriber.LastEvents.Count > 0) - { - subscriptionPosition = subscriber.LastPosition; - - return subscriber.LastEvents; - } - } - - cts.Token.ThrowIfCancellationRequested(); - - return null; - } - } - finally - { - subscription?.Dispose(); - } - } - - private async Task> AppendEventsAsync(string streamName, int count, int commits = 1) - { - var events = new List(); - - for (var i = 0; i < count; i++) - { - events.Add(CreateEventData(i)); - } - - for (var i = 0; i < events.Count / commits; i++) - { - var commit = events.Skip(i * commits).Take(commits).ToArray(); - - await Sut.AppendAsync(Guid.NewGuid(), streamName, EtagVersion.Any, commit); - } - - return events; - } - - private static void ShouldBeEquivalentTo(IEnumerable? actual, params StoredEvent[] expected) - { - actual.Should().BeEquivalentTo(expected, opts => opts.ComparingByMembers().Excluding(x => x.EventPosition)); - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreFixture.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreFixture.cs deleted file mode 100644 index e91dc4ee08..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreFixture.cs +++ /dev/null @@ -1,47 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using EventStore.Client; -using Squidex.Infrastructure.TestHelpers; - -namespace Squidex.Infrastructure.EventSourcing; - -public sealed class GetEventStoreFixture : IAsyncLifetime -{ - private readonly EventStoreClientSettings settings; - - public GetEventStore EventStore { get; } - - public GetEventStoreFixture() - { - AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); - - settings = EventStoreClientSettings.Create(TestConfig.Configuration["eventStore:configuration"]!); - - EventStore = new GetEventStore(settings, TestUtils.DefaultSerializer); - } - - public Task InitializeAsync() - { - return EventStore.InitializeAsync(default); - } - - public async Task DisposeAsync() - { - var projectionsManager = new EventStoreProjectionManagementClient(settings); - - await foreach (var projection in projectionsManager.ListAllAsync()) - { - var name = projection.Name; - - if (name.StartsWith("by-squidex-test", StringComparison.OrdinalIgnoreCase)) - { - await projectionsManager.DisableAsync(name); - } - } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreTests.cs deleted file mode 100644 index 3348e09bfa..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/GetEventStoreTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable SA1300 // Element should begin with upper-case letter - -namespace Squidex.Infrastructure.EventSourcing; - -[Trait("Category", "Dependencies")] -public class GetEventStoreTests(GetEventStoreFixture fixture) : EventStoreTests, IClassFixture -{ - public GetEventStoreFixture _ { get; } = fixture; - - public override GetEventStore CreateStore() - { - return _.EventStore; - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreTests.cs deleted file mode 100644 index 7270581765..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/MongoEventStoreTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable SA1300 // Element should begin with upper-case letter -#pragma warning disable MA0048 // File name must match type name - -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Infrastructure.EventSourcing; - -public abstract class MongoEventStoreTests(MongoEventStoreFixture fixture) : EventStoreTests, IAsyncLifetime -{ - private ProfilerCollection profiler; - - public MongoEventStoreFixture _ { get; } = fixture; - - public override MongoEventStore CreateStore() - { - return _.EventStore; - } - - public Task InitializeAsync() - { - profiler = new ProfilerCollection(_.Database); - - return profiler.ClearAsync(); - } - - public async Task DisposeAsync() - { - var queries = await profiler.GetQueriesAsync("Events2"); - - Assert.All(queries, query => - { - Assert.InRange(query.DocsExamined, 0, query.NumDocuments); - Assert.InRange(query.KeysExamined, query.NumDocuments, (Math.Max(1, query.NumDocuments) * 2) + 1); - }); - } -} - -[Trait("Category", "Dependencies")] -public sealed class MongoEventStoreTests_Direct(MongoEventStoreFixture_Direct fixture) : MongoEventStoreTests(fixture), IClassFixture -{ -} - -[Trait("Category", "Dependencies")] -public sealed class MongoEventStoreTests_Replica(MongoEventStoreFixture_Replica fixture) : MongoEventStoreTests(fixture), IClassFixture -{ -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs deleted file mode 100644 index c5f65e29da..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/PollingSubscriptionTests.cs +++ /dev/null @@ -1,156 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Globalization; - -namespace Squidex.Infrastructure.EventSourcing; - -public class PollingSubscriptionTests -{ - private readonly IEventStore eventStore = A.Fake(); - private readonly IEventSubscriber eventSubscriber = A.Fake>(); - private readonly StreamFilter filter = StreamFilter.Name("my-stream"); - private readonly string position = Guid.NewGuid().ToString(); - - [Fact] - public async Task Should_subscribe_on_start() - { - await SubscribeAsync(false); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .MustHaveHappenedOnceExactly(); - } - - [Fact] - public async Task Should_forward_exception_to_subscriber() - { - var ex = new InvalidOperationException(); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .Throws(ex); - - var sut = await SubscribeAsync(false); - - A.CallTo(() => eventSubscriber.OnErrorAsync(sut, ex)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_forward_operation_cancelled_exception_to_subscriber() - { - var ex = new OperationCanceledException(); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .Throws(ex); - - var sut = await SubscribeAsync(false); - - A.CallTo(() => eventSubscriber.OnErrorAsync(sut, ex)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_forward_aggregate_operation_cancelled_exception_to_subscriber() - { - var ex = new AggregateException(new OperationCanceledException()); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .Throws(ex); - - var sut = await SubscribeAsync(false); - - A.CallTo(() => eventSubscriber.OnErrorAsync(sut, ex)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_wake_up() - { - var sut = await SubscribeAsync(true); - - A.CallTo(() => eventStore.QueryAllAsync(filter, A._, A._, A._)) - .MustHaveHappened(2, Times.Exactly); - } - - [Fact] - public async Task Should_forward_events_to_subscriber() - { - var events = Enumerable.Range(0, 50).Select(CreateEvent).ToArray(); - - var receivedEvents = new List(); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .Returns(events.ToAsyncEnumerable()); - - A.CallTo(() => eventSubscriber.OnNextAsync(A._, A._)) - .Invokes(x => receivedEvents.Add(x.GetArgument(1)!)); - - await SubscribeAsync(true); - - receivedEvents.Should().BeEquivalentTo(events); - } - - [Fact] - public async Task Should_continue_on_last_position() - { - var events1 = Enumerable.Range(10, 10).Select(CreateEvent).ToArray(); - var events2 = Enumerable.Range(20, 10).Select(CreateEvent).ToArray(); - - var lastPosition = events1[^1].EventPosition; - - var receivedEvents = new List(); - - A.CallTo(() => eventStore.QueryAllAsync(filter, position, A._, A._)) - .Returns(events1.ToAsyncEnumerable()); - - A.CallTo(() => eventStore.QueryAllAsync(filter, lastPosition, A._, A._)) - .Returns(events2.ToAsyncEnumerable()); - - A.CallTo(() => eventSubscriber.OnNextAsync(A._, A._)) - .Invokes(x => receivedEvents.Add(x.GetArgument(1)!)); - - await SubscribeAsync(true); - - receivedEvents.Should().BeEquivalentTo(events1.Union(events2)); - } - - private StoredEvent CreateEvent(int position) - { - return new StoredEvent( - "my-stream", - position.ToString(CultureInfo.InvariantCulture)!, - position, - new EventData( - "type", - new EnvelopeHeaders - { - [CommonHeaders.EventId] = Guid.NewGuid().ToString() - }, - "payload")); - } - - private async Task SubscribeAsync(bool wakeup = true) - { - var sut = new PollingSubscription(eventStore, eventSubscriber, filter, position); - - try - { - if (wakeup) - { - sut.WakeUp(); - } - - await Task.Delay(200); - } - finally - { - sut.Dispose(); - } - - return sut; - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs deleted file mode 100644 index 50a73fed90..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -public class RetrySubscriptionTests -{ - private readonly IEventStore eventStore = A.Fake(); - private readonly IEventSubscriber eventSubscriber = A.Fake>(); - private readonly IEventSubscription eventSubscription = A.Fake(); - private readonly IEventSubscriber sutSubscriber; - private readonly RetrySubscription sut; - - public RetrySubscriptionTests() - { - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) - .Returns(eventSubscription); - - sut = new RetrySubscription(eventSubscriber, s => eventStore.CreateSubscription(s, default)) { ReconnectWaitMs = 50 }; - sutSubscriber = sut; - } - - [Fact] - public void Should_subscribe_after_constructor() - { - sut.Dispose(); - - A.CallTo(() => eventStore.CreateSubscription(sut, A._, A._)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_reopen_subscription_once_if_exception_is_retrieved() - { - var ex = new InvalidOperationException(); - - await OnErrorAsync(eventSubscription, ex, times: 1); - - await Task.Delay(1000); - - sut.Dispose(); - - A.CallTo(() => eventSubscription.Dispose()) - .MustHaveHappened(2, Times.Exactly); - - A.CallTo(() => eventStore.CreateSubscription(A>._, A._, A._)) - .MustHaveHappened(2, Times.Exactly); - - A.CallTo(() => eventSubscriber.OnErrorAsync(eventSubscription, A._)) - .MustNotHaveHappened(); - } - - [Fact] - public async Task Should_forward_error_from_inner_subscription_if_failed_often() - { - var ex = new InvalidOperationException(); - - await OnErrorAsync(eventSubscription, ex, times: 6); - - sut.Dispose(); - - A.CallTo(() => eventSubscriber.OnErrorAsync(sut, ex)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_ignore_operation_cancelled_error_from_inner_subscription_if_failed_often() - { - var ex = new OperationCanceledException(); - - await OnErrorAsync(eventSubscription, ex, times: 6); - - sut.Dispose(); - - A.CallTo(() => eventSubscriber.OnErrorAsync(sut, ex)) - .MustNotHaveHappened(); - } - - [Fact] - public async Task Should_not_forward_error_if_exception_is_raised_after_unsubscribe() - { - var ex = new InvalidOperationException(); - - await OnErrorAsync(eventSubscription, ex, times: 1); - - sut.Dispose(); - - A.CallTo(() => eventSubscriber.OnErrorAsync(eventSubscription, A._)) - .MustNotHaveHappened(); - } - - [Fact] - public async Task Should_forward_event_from_inner_subscription() - { - var @event = new StoredEvent("Stream", "1", 2, new EventData("Type", [], "Payload")); - - await OnNextAsync(eventSubscription, @event); - - sut.Dispose(); - - A.CallTo(() => eventSubscriber.OnNextAsync(sut, @event)) - .MustHaveHappened(); - } - - [Fact] - public async Task Should_not_forward_event_if_message_is_from_another_subscription() - { - var @event = new StoredEvent("Stream", "1", 2, new EventData("Type", [], "Payload")); - - await OnNextAsync(A.Fake(), @event); - - sut.Dispose(); - - A.CallTo(() => eventSubscriber.OnNextAsync(A._, A._)) - .MustNotHaveHappened(); - } - - [Fact] - public async Task Should_be_able_to_unsubscribe_within_exception_handler() - { - var ex = new InvalidOperationException(); - - A.CallTo(() => eventSubscriber.OnErrorAsync(A._, A._)) - .Invokes(() => sut.Dispose()); - - await OnErrorAsync(eventSubscription, ex, times: 6); - - Assert.False(sut.IsSubscribed); - } - - [Fact] - public async Task Should_be_able_to_unsubscribe_within_event_handler() - { - var @event = new StoredEvent("Stream", "1", 2, new EventData("Type", [], "Payload")); - - A.CallTo(() => eventSubscriber.OnNextAsync(A._, A._)) - .Invokes(() => sut.Dispose()); - - await OnNextAsync(eventSubscription, @event); - - Assert.False(sut.IsSubscribed); - } - - private async ValueTask OnErrorAsync(IEventSubscription subscriber, Exception ex, int times) - { - for (var i = 0; i < times; i++) - { - await sutSubscriber.OnErrorAsync(subscriber, ex); - } - } - - private ValueTask OnNextAsync(IEventSubscription subscriber, StoredEvent ev) - { - return sutSubscriber.OnNextAsync(subscriber, ev); - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/StreamFilterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/StreamFilterTests.cs deleted file mode 100644 index 45c310c631..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/EventSourcing/StreamFilterTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.EventSourcing; - -public class StreamFilterTests -{ - [Fact] - public void Should_simplify_input_to_default_filter() - { - var sut = new StreamFilter(StreamFilterKind.MatchFull); - - Assert.Equal(default, sut); - } - - [Fact] - public void Should_simplify_input_to_default_filter_with_factory() - { - var sut = StreamFilter.Name(); - - Assert.Equal(default, sut); - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs index a8de8e0873..9d1a6b30ff 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs @@ -31,7 +31,7 @@ public void Should_serialize_and_deserialize() "Google") ]); - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value.Identities.ElementAt(0).AuthenticationType, serialized.Identities.ElementAt(0).AuthenticationType); Assert.Equal(value.Identities.ElementAt(1).AuthenticationType, serialized.Identities.ElementAt(1).AuthenticationType); @@ -42,7 +42,7 @@ public void Should_serialize_and_deserialize_null_principal() { ClaimsPrincipal? value = null; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Null(serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs index 7e02f024a3..98ba888cc5 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs @@ -17,7 +17,7 @@ public void Should_serialize_and_deserialize() { var value = Instant.FromUtc(2012, 12, 10, 9, 8, 45); - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } @@ -27,7 +27,7 @@ public void Should_serialize_and_deserialize_nullable_with_value() { Instant? value = Instant.FromUtc(2012, 12, 10, 9, 8, 45); - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } @@ -37,7 +37,7 @@ public void Should_serialize_and_deserialize_nullable_with_null() { Instant? value = null; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs index c02224d976..228e4e7b52 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs @@ -5,141 +5,107 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.TestHelpers; namespace Squidex.Infrastructure.Json.Objects; public class JsonValuesSerializationTests { - public enum SerializerMode - { - Json, - Bson - } - - public static readonly TheoryData Serializers = new TheoryData - { - { SerializerMode.Json }, - { SerializerMode.Bson } - }; - - private static T Serialize(T input, SerializerMode mode) - { - if (mode == SerializerMode.Bson) - { - return input.SerializeAndDeserializeBson(); - } - else - { - return input.SerializeAndDeserialize(); - } - } - [Fact] public void Should_deserialize_integer() { var value = 123; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(JsonValue.Create(value), serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_null(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_null() { var value = JsonValue.Null; - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_date(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_date() { var value = JsonValue.Create("2008-09-15T15:53:00"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_string(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_string() { var value = JsonValue.Create("my-string"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_boolean(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_boolean() { var value = JsonValue.Create(true); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_number(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_number() { var value = JsonValue.Create(123); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_double_number(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_double_number() { var value = JsonValue.Create(123.5); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_array(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_array() { var value = JsonValue.Array(1, 2); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_object(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_object() { var value = JsonValue.Object() .Add("1", 1) .Add("2", 1); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_complex_object(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_complex_object() { var value = JsonValue.Object() @@ -150,25 +116,8 @@ public void Should_serialize_and_deserialize_complex_object(SerializerMode mode) .Add("2", JsonValue.Object().Add("2_1", 11)); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - - [Fact] - public void Should_deserialize_from_escaped_dot() - { - var value = new Dictionary - { - ["key.with.dot".JsonToBsonName()] = 10 - }; - - var expected = - JsonValue.Object() - .Add("key.with.dot", 10); - - var serialized = TestUtils.SerializeAndDeserializeBson>(value); - - Assert.Equal(expected, serialized); - } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Json/System/ReadOnlyCollectionTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Json/System/ReadOnlyCollectionTests.cs index afccda8cdb..8e7a0b1cef 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Json/System/ReadOnlyCollectionTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Json/System/ReadOnlyCollectionTests.cs @@ -28,7 +28,7 @@ public void Should_serialize_and_deserialize_dictionary() } }; - var serialized = source.SerializeAndDeserialize(); + var serialized = source.SerializeAndDeserializeJson(); Assert.Equal(2, serialized.Values.Count); } @@ -45,7 +45,7 @@ public void Should_serialize_and_deserialize_list_without_type_name() } }; - var serialized = source.SerializeAndDeserialize(); + var serialized = source.SerializeAndDeserializeJson(); Assert.Equal(2, serialized.Values.Count); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/LanguageTests.cs b/backend/tests/Squidex.Infrastructure.Tests/LanguageTests.cs index 9acb9acd8b..2c76843d04 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/LanguageTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/LanguageTests.cs @@ -139,7 +139,7 @@ public void Should_serialize_and_deserialize_null_language() { Language? value = null; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } @@ -149,7 +149,7 @@ public void Should_serialize_and_deserialize_valid_language() { var value = Language.DE; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/Entities.cs b/backend/tests/Squidex.Infrastructure.Tests/MongoDb/Entities.cs deleted file mode 100644 index c0cc46ad29..0000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/MongoDb/Entities.cs +++ /dev/null @@ -1,49 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; - -namespace Squidex.Infrastructure.MongoDb; - -public static class Entities -{ - public sealed class DateTimeEntity - { - [BsonRepresentation(BsonType.DateTime)] - public T Value { get; set; } - } - - public sealed class Int64Entity - { - [BsonRepresentation(BsonType.Int64)] - public T Value { get; set; } - } - - public sealed class Int32Entity - { - [BsonRepresentation(BsonType.Int32)] - public T Value { get; set; } - } - - public sealed class StringEntity - { - [BsonRepresentation(BsonType.String)] - public T Value { get; set; } - } - - public sealed class BinaryEntity - { - [BsonRepresentation(BsonType.Binary)] - public T Value { get; set; } - } - - public sealed class DefaultEntity - { - public T Value { get; set; } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs b/backend/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs index 8f2f492fdb..0e767775f7 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs @@ -8,7 +8,6 @@ using System.Text.Json.Serialization; using Squidex.Infrastructure.Json.System; using Squidex.Infrastructure.TestHelpers; -using static Squidex.Infrastructure.Json.Objects.JsonValuesSerializationTests; namespace Squidex.Infrastructure; @@ -20,24 +19,6 @@ internal sealed record Wrapper public NamedId Value { get; set; } } - public static readonly TheoryData Serializers = new TheoryData - { - { SerializerMode.Json }, - { SerializerMode.Bson } - }; - - private static T Serialize(T input, SerializerMode mode) - { - if (mode == SerializerMode.Bson) - { - return input.SerializeAndDeserializeBson(); - } - else - { - return input.SerializeAndDeserialize(); - } - } - [Fact] public void Should_instantiate_token() { @@ -59,90 +40,82 @@ public void Should_convert_named_id_to_string() Assert.Equal($"{id},my-name", namedId.ToString()); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_null_guid_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_null_guid_token() { NamedId? value = null; - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_valid_guid_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_valid_guid_token() { var value = NamedId.Of(Guid.NewGuid(), "my-name"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_null_long_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_null_long_token() { NamedId? value = null; - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_valid_long_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_valid_long_token() { var value = NamedId.Of(123L, "my-name"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_null_string_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_null_string_token() { NamedId? value = null; - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_valid_string_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_valid_string_token() { var value = NamedId.Of(Guid.NewGuid().ToString(), "my-name"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_null_id_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_null_id_token() { NamedId? value = null; - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } - [Theory] - [MemberData(nameof(Serializers))] - public void Should_serialize_and_deserialize_valid_id_token(SerializerMode mode) + [Fact] + public void Should_serialize_and_deserialize_valid_id_token() { var value = NamedId.Of(DomainId.NewGuid().ToString(), "my-name"); - var serialized = Serialize(value, mode); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } @@ -152,7 +125,7 @@ public void Should_serialize_and_deserialize_old_object() { var value = new { id = 42L, name = "my-name" }; - var serialized = value.SerializeAndDeserialize, object>(); + var serialized = value.SerializeAndDeserializeJson, object>(); Assert.Equal(NamedId.Of(42L, "my-name"), serialized); } @@ -170,26 +143,26 @@ public void Should_deserialize_from_old_object_with_explicit_converter() Value = NamedId.Of(42L, "my-name") }; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(expected, serialized); } [Fact] - public void Should_throw_exception_if_string_id_is_not_valid() + public void Should_throw_exception_if_string_id_is_not_valid_json() { - Assert.ThrowsAny(() => TestUtils.Deserialize>("123")); + Assert.ThrowsAny(() => TestUtils.DeserializeJson>("123")); } [Fact] - public void Should_throw_exception_if_long_id_is_not_valid() + public void Should_throw_exception_if_long_id_is_not_valid_json() { - Assert.ThrowsAny(() => TestUtils.Deserialize>("invalid-long,name")); + Assert.ThrowsAny(() => TestUtils.DeserializeJson>("invalid-long,name")); } [Fact] - public void Should_throw_exception_if_guid_id_is_not_valid() + public void Should_throw_exception_if_guid_id_is_not_valid_json() { - Assert.ThrowsAny(() => TestUtils.Deserialize>("invalid-guid,name")); + Assert.ThrowsAny(() => TestUtils.DeserializeJson>("invalid-guid,name")); } } diff --git a/backend/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs b/backend/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs index a5e25eee07..89afb6674b 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs @@ -95,7 +95,7 @@ public void Should_serialize_and_deserialize_null_token() { RefToken? value = null; - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } @@ -105,7 +105,7 @@ public void Should_serialize_and_deserialize_valid_token() { var value = RefToken.Parse("client:client1"); - var serialized = value.SerializeAndDeserialize(); + var serialized = value.SerializeAndDeserializeJson(); Assert.Equal(value, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs index add40d8be2..95652983db 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Security/PermissionSetTests.cs @@ -137,7 +137,7 @@ public void Should_serialize_and_deserialize() { var permissions = new PermissionSet("a", "b", "c"); - var serialized = permissions.SerializeAndDeserialize(); + var serialized = permissions.SerializeAndDeserializeJson(); Assert.Equal(permissions, serialized); } diff --git a/backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj b/backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj index 1113424885..22665a5b25 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj +++ b/backend/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj @@ -9,13 +9,11 @@ en - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs index bf6519f5c4..09bd14d6b4 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceBatchTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Globalization; +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.TestHelpers; diff --git a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs index 5830aa553e..d0251c3957 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using System.Globalization; +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.TestHelpers; diff --git a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs index f77683b888..5528cef903 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/States/PersistenceSnapshotTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Events; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.States; diff --git a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestUtils.cs b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestUtils.cs index 39e57d21b7..870b97bf7b 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestUtils.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/TestHelpers/TestUtils.cs @@ -8,15 +8,12 @@ using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; -using MongoDB.Bson.IO; -using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Attributes; using NodaTime; using NodaTime.Serialization.SystemTextJson; +using Squidex.Events.Utils; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.System; -using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries.Json; @@ -28,31 +25,13 @@ public static class TestUtils { public static readonly IJsonSerializer DefaultSerializer = CreateSerializer(); - public sealed class ObjectHolder + private sealed class ObjectHolder { - [BsonRequired] public T Value1 { get; set; } - [BsonRequired] public T Value2 { get; set; } } - static TestUtils() - { - SetupBson(); - } - - public static void SetupBson() - { - BsonDefaultConventions.Register(); - BsonDomainIdSerializer.Register(); - BsonEscapedDictionarySerializer.Register(); - BsonInstantSerializer.Register(); - BsonJsonConvention.Register(DefaultOptions()); - BsonJsonValueSerializer.Register(); - BsonStringSerializer.Register(); - } - public static IJsonSerializer CreateSerializer(Action? configure = null) { var serializerSettings = DefaultOptions(configure); @@ -69,6 +48,7 @@ public static JsonSerializerOptions DefaultOptions(Action options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); options.Converters.Add(new JsonValueConverter()); + options.Converters.Add(new HeaderValueConverter()); options.Converters.Add(new ReadonlyDictionaryConverterFactory()); options.Converters.Add(new ReadonlyListConverterFactory()); options.Converters.Add(new SurrogateJsonConverter()); @@ -87,41 +67,19 @@ public static JsonSerializerOptions DefaultOptions(Action return options; } - public static T SerializeAndDeserializeBson(this T value) - { - return SerializeAndDeserializeBson(value); - } - - public static TOut SerializeAndDeserializeBson(this TIn value) - { - using var stream = new MemoryStream(); - - using (var writer = new BsonBinaryWriter(stream)) - { - BsonSerializer.Serialize(writer, new ObjectHolder { Value1 = value, Value2 = value }); - } - - stream.Position = 0; - - using (var reader = new BsonBinaryReader(stream)) - { - return BsonSerializer.Deserialize>(reader).Value1; - } - } - - public static T SerializeAndDeserialize(this T value) + public static T SerializeAndDeserializeJson(this T value) { - return SerializeAndDeserialize(value); + return SerializeAndDeserializeJson(value); } - public static TOut SerializeAndDeserialize(this TIn value) + public static TOut SerializeAndDeserializeJson(this TIn value) { var json = DefaultSerializer.Serialize(new ObjectHolder { Value1 = value, Value2 = value }); return DefaultSerializer.Deserialize>(json).Value1; } - public static T Deserialize(string value) + public static T DeserializeJson(string value) { var json = DefaultSerializer.Serialize(new ObjectHolder { Value1 = value, Value2 = value }); diff --git a/backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj b/backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj index 0babef9304..38548acfe0 100644 --- a/backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj +++ b/backend/tests/Squidex.Web.Tests/Squidex.Web.Tests.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tools/e2e/package-lock.json b/tools/e2e/package-lock.json index b46297f824..32bef313b0 100644 --- a/tools/e2e/package-lock.json +++ b/tools/e2e/package-lock.json @@ -12,7 +12,7 @@ "uuid": "^10.0.0" }, "devDependencies": { - "@playwright/test": "^1.40.0", + "@playwright/test": "^1.49.1", "@types/node": "^20.9.3", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.12.0", @@ -157,18 +157,18 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.0.tgz", - "integrity": "sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "dependencies": { - "playwright": "1.40.0" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@types/json-schema": { @@ -2267,33 +2267,33 @@ } }, "node_modules/playwright": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.0.tgz", - "integrity": "sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "dependencies": { - "playwright-core": "1.40.0" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", - "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/prelude-ls": { diff --git a/tools/e2e/package.json b/tools/e2e/package.json index 3a00f3f4d1..7ada8bcca4 100644 --- a/tools/e2e/package.json +++ b/tools/e2e/package.json @@ -12,7 +12,7 @@ "author": "Sebastian", "license": "ISC", "devDependencies": { - "@playwright/test": "^1.40.0", + "@playwright/test": "^1.49.1", "@types/node": "^20.9.3", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.12.0", diff --git a/tools/e2e/tests/login-start.spec.ts b/tools/e2e/tests/login-start.spec.ts index 3431f7ce61..1b670494de 100644 --- a/tools/e2e/tests/login-start.spec.ts +++ b/tools/e2e/tests/login-start.spec.ts @@ -16,5 +16,5 @@ test('has title', async ({ page }) => { }); test('visual test', async ({ page }) => { - await expect(page).toHaveScreenshot(); + await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.05 }); }); \ No newline at end of file diff --git a/tools/e2e/tests/login.spec.ts b/tools/e2e/tests/login.spec.ts index 8d27e0f3b6..e102898197 100644 --- a/tools/e2e/tests/login.spec.ts +++ b/tools/e2e/tests/login.spec.ts @@ -25,5 +25,5 @@ test('login', async ({ page, loginPage }) => { test('visual test', async ({ loginPage }) => { const popup = await loginPage.openPopup(); - await expect(popup.root).toHaveScreenshot({ fullPage: true }); + await expect(popup.root).toHaveScreenshot({ maxDiffPixelRatio: 0.05, fullPage: true }); }); \ No newline at end of file