diff --git a/Dockerfile b/Dockerfile index 3dd086889..be4c10abb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,8 +47,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \ RUN dotnet restore \ && dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \ && dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \ - && dotnet test tests/Squidex.Domain.Apps.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj \ - && dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \ + && dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \ && dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj # Publish diff --git a/Dockerfile.build b/Dockerfile.build index 83f9e11b3..f09cbb2bb 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -44,8 +44,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \ RUN dotnet restore \ && dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \ && dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \ - && dotnet test tests/Squidex.Domain.Apps.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj \ - && dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \ + && dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \ && dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj # Publish diff --git a/Squidex.sln b/Squidex.sln index 21ac0f2e4..72eff25c0 100644 --- a/Squidex.sln +++ b/Squidex.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 +VisualStudioVersion = 15.0.27130.2003 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}" EndProject @@ -12,22 +12,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Events", "src\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj", "{25F66C64-058A-4D44-BC0C-F12A054F9A91}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Write", "src\Squidex.Domain.Apps.Write\Squidex.Domain.Apps.Write.csproj", "{A85201C6-6AF8-4B63-8365-08F741050438}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Read", "src\Squidex.Domain.Apps.Read\Squidex.Domain.Apps.Read.csproj", "{A92B4734-2587-4F6F-97A3-741BE48709A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Read.MongoDb", "src\Squidex.Domain.Apps.Read.MongoDb\Squidex.Domain.Apps.Read.MongoDb.csproj", "{28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Write.Tests", "tests\Squidex.Domain.Apps.Write.Tests\Squidex.Domain.Apps.Write.Tests.csproj", "{9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Tests", "tests\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.csproj", "{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}" 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Read.Tests", "tests\Squidex.Domain.Apps.Read.Tests\Squidex.Domain.Apps.Read.Tests.csproj", "{8B074219-F69A-4E41-83C6-12EE1E647779}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Redis", "src\Squidex.Infrastructure.Redis\Squidex.Infrastructure.Redis.csproj", "{D7166C56-178A-4457-B56A-C615C7450DEE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.csproj", "{C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}" @@ -36,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Goog EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", "{94207AA6-4923-4183-A558-E0F8196B8CA3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migrate_01", "tools\Migrate_01\Migrate_01.csproj", "{B51126A8-0D75-4A79-867D-10724EC6AC84}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migrate_00", "tools\Migrate_00\Migrate_00.csproj", "{B51126A8-0D75-4A79-867D-10724EC6AC84}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Shared", "src\Squidex.Shared\Squidex.Shared.csproj", "{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}" EndProject @@ -63,7 +53,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Core.Mo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Core.Operations", "src\Squidex.Domain.Apps.Core.Operations\Squidex.Domain.Apps.Core.Operations.csproj", "{6B3F75B6-5888-468E-BA4F-4FC725DAEF31}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "tests\Benchmarks\Benchmarks.csproj", "{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Entities", "src\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj", "{79FEF326-CA5E-4698-B2BA-C16A4580B4D5}" +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}") = "Migrate_01", "tools\Migrate_01\Migrate_01.csproj", "{A4823E14-C0E5-4A4D-B28F-27424C25C3C7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -99,38 +95,6 @@ Global {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|Any CPU.Build.0 = Release|Any CPU {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|x64.ActiveCfg = Release|Any CPU {25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|x86.ActiveCfg = Release|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|x64.ActiveCfg = Debug|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Debug|x86.ActiveCfg = Debug|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Release|Any CPU.Build.0 = Release|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Release|x64.ActiveCfg = Release|Any CPU - {A85201C6-6AF8-4B63-8365-08F741050438}.Release|x86.ActiveCfg = Release|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Debug|x64.ActiveCfg = Debug|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Debug|x86.ActiveCfg = Debug|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Release|Any CPU.Build.0 = Release|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Release|x64.ActiveCfg = Release|Any CPU - {A92B4734-2587-4F6F-97A3-741BE48709A5}.Release|x86.ActiveCfg = Release|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Debug|x64.ActiveCfg = Debug|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Debug|x86.ActiveCfg = Debug|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Release|Any CPU.Build.0 = Release|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Release|x64.ActiveCfg = Release|Any CPU - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD}.Release|x86.ActiveCfg = Release|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Debug|x64.ActiveCfg = Debug|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Debug|x86.ActiveCfg = Debug|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Release|Any CPU.Build.0 = Release|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Release|x64.ActiveCfg = Release|Any CPU - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203}.Release|x86.ActiveCfg = Release|Any CPU {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -155,14 +119,6 @@ Global {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 - {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|x64.ActiveCfg = Debug|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Debug|x86.ActiveCfg = Debug|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|Any CPU.Build.0 = Release|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|x64.ActiveCfg = Release|Any CPU - {8B074219-F69A-4E41-83C6-12EE1E647779}.Release|x86.ActiveCfg = Release|Any CPU {D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -303,18 +259,54 @@ Global {6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x64.Build.0 = Release|Any CPU {6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x86.ActiveCfg = Release|Any CPU {6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x86.Build.0 = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.ActiveCfg = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.Build.0 = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.ActiveCfg = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.Build.0 = Debug|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.Build.0 = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.ActiveCfg = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.Build.0 = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.ActiveCfg = Release|Any CPU - {9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.Build.0 = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x64.ActiveCfg = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x64.Build.0 = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x86.ActiveCfg = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x86.Build.0 = Debug|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|Any CPU.Build.0 = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x64.ActiveCfg = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x64.Build.0 = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x86.ActiveCfg = Release|Any CPU + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x86.Build.0 = Release|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|x64.ActiveCfg = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|x64.Build.0 = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|x86.ActiveCfg = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Debug|x86.Build.0 = Debug|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|Any CPU.Build.0 = Release|Any CPU + {AA003372-CD8D-4DBC-962C-F61E0C93CF05}.Release|x64.ActiveCfg = Release|Any CPU + {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 + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|x64.Build.0 = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Debug|x86.Build.0 = Debug|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|Any CPU.Build.0 = Release|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|x64.ActiveCfg = Release|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|x64.Build.0 = Release|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|x86.ActiveCfg = Release|Any CPU + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -322,14 +314,9 @@ Global GlobalSection(NestedProjects) = preSolution {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {25F66C64-058A-4D44-BC0C-F12A054F9A91} = {C9809D59-6665-471E-AD87-5AC624C65892} - {A85201C6-6AF8-4B63-8365-08F741050438} = {C9809D59-6665-471E-AD87-5AC624C65892} - {A92B4734-2587-4F6F-97A3-741BE48709A5} = {C9809D59-6665-471E-AD87-5AC624C65892} - {28F8E9E2-FE24-41F7-A888-9FC244A9E2DD} = {C9809D59-6665-471E-AD87-5AC624C65892} - {9A3DEA7E-1681-4D48-AC5C-1F0DE421A203} = {C9809D59-6665-471E-AD87-5AC624C65892} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {C9809D59-6665-471E-AD87-5AC624C65892} {6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} - {8B074219-F69A-4E41-83C6-12EE1E647779} = {C9809D59-6665-471E-AD87-5AC624C65892} {D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {C1E5BBB6-6B6A-4DE5-B19D-0538304DE343} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {945871B1-77B8-43FB-B53C-27CF385AB756} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} @@ -344,6 +331,10 @@ Global {7931187E-A1E6-4F89-8BC8-20A1E445579F} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {F0A83301-50A5-40EA-A1A2-07C7858F5A3F} = {C9809D59-6665-471E-AD87-5AC624C65892} {6B3F75B6-5888-468E-BA4F-4FC725DAEF31} = {C9809D59-6665-471E-AD87-5AC624C65892} + {79FEF326-CA5E-4698-B2BA-C16A4580B4D5} = {C9809D59-6665-471E-AD87-5AC624C65892} + {AA003372-CD8D-4DBC-962C-F61E0C93CF05} = {C9809D59-6665-471E-AD87-5AC624C65892} + {7DA5B308-D950-4496-93D5-21D6C4D91644} = {C9809D59-6665-471E-AD87-5AC624C65892} + {A4823E14-C0E5-4A4D-B28F-27424C25C3C7} = {94207AA6-4923-4183-A558-E0F8196B8CA3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08} diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs index 548a88ae3..aa1fece95 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs @@ -31,15 +31,15 @@ namespace Squidex.Domain.Apps.Core.Contents { } - protected static TResult Merge(TResult target, TResult source1, TResult source2) where TResult : ContentData + protected static TResult MergeTo(TResult target, params TResult[] sources) where TResult : ContentData { - if (ReferenceEquals(source1, source2)) + Guard.NotEmpty(sources, nameof(sources)); + + if (sources.Length == 1 || sources.Skip(1).All(x => ReferenceEquals(x, sources[0]))) { - return source1; + return sources[0]; } - var sources = new[] { source1, source2 }; - foreach (var source in sources) { foreach (var otherValue in source) diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs index e04e88310..20fbfe83f 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs @@ -24,9 +24,14 @@ namespace Squidex.Domain.Apps.Core.Contents { } + public static IdContentData Merge(params IdContentData[] contents) + { + return MergeTo(new IdContentData(), contents); + } + public IdContentData MergeInto(IdContentData target) { - return Merge(new IdContentData(), this, target); + return Merge(target, this); } public IdContentData ToCleaned() diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs index e6f452d66..7318c8214 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs @@ -24,9 +24,14 @@ namespace Squidex.Domain.Apps.Core.Contents { } + public static NamedContentData Merge(params NamedContentData[] contents) + { + return MergeTo(new NamedContentData(), contents); + } + public NamedContentData MergeInto(NamedContentData target) { - return Merge(new NamedContentData(), this, target); + return Merge(target, this); } public NamedContentData ToCleaned() diff --git a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index bd76d3726..ac30c2d2e 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs new file mode 100644 index 000000000..e5b359397 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// MongoAppEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Entities.Apps.State; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Apps +{ + public sealed class MongoAppEntity + { + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; set; } + + [BsonElement] + [BsonRequired] + public int Version { get; set; } + + [BsonElement] + [BsonRequired] + public string Name { get; set; } + + [BsonElement] + [BsonRequired] + public string[] UserIds { get; set; } + + [BsonJson] + [BsonElement] + [BsonRequired] + public AppState State { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs new file mode 100644 index 000000000..1adb16a06 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs @@ -0,0 +1,104 @@ +// ========================================================================== +// MongoAppRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Apps.Repositories; +using Squidex.Domain.Apps.Entities.Apps.State; +using Squidex.Infrastructure; +using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Apps +{ + public sealed class MongoAppRepository : MongoRepositoryBase, IAppRepository, ISnapshotStore + { + public MongoAppRepository(IMongoDatabase database) + : base(database) + { + } + + protected override string CollectionName() + { + return "States_Apps"; + } + + protected override async Task SetupCollectionAsync(IMongoCollection collection) + { + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.UserIds)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); + } + + public async Task FindAppIdByNameAsync(string name) + { + var appEntity = + await Collection.Find(x => x.Name == name).Only(x => x.Id) + .FirstOrDefaultAsync(); + + return appEntity != null ? Guid.Parse(appEntity["_id"].AsString) : Guid.Empty; + } + + public async Task> QueryUserAppIdsAsync(string userId) + { + var appEntities = + await Collection.Find(x => x.UserIds.Contains(userId)).Only(x => x.Id) + .ToListAsync(); + + return appEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); + } + + public async Task<(AppState Value, long Version)> ReadAsync(Guid key) + { + var existing = + await Collection.Find(x => x.Id == key) + .FirstOrDefaultAsync(); + + if (existing != null) + { + return (existing.State, existing.Version); + } + + return (null, EtagVersion.NotFound); + } + + public async Task WriteAsync(Guid key, AppState value, long oldVersion, long newVersion) + { + try + { + await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, + Update + .Set(x => x.UserIds, value.Contributors.Keys.ToArray()) + .Set(x => x.Name, value.Name) + .Set(x => x.State, value) + .Set(x => x.Version, newVersion), + Upsert); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == key) + .Project(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion.Version, oldVersion, ex); + } + } + else + { + throw; + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs new file mode 100644 index 000000000..923669eb0 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// MongoAssetEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Entities.Assets.State; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Assets +{ + public sealed class MongoAssetEntity + { + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; set; } + + [BsonElement] + [BsonRequired] + public AssetState State { get; set; } + + [BsonElement] + [BsonRequired] + public int Version { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs similarity index 50% rename from src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 73bb48ae8..f632f8d81 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -12,13 +12,16 @@ using System.Linq; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Assets.State; +using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.States; -namespace Squidex.Domain.Apps.Read.MongoDb.Assets +namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { - public partial class MongoAssetRepository : MongoRepositoryBase, IAssetRepository, IAssetEventConsumer + public sealed class MongoAssetRepository : MongoRepositoryBase, IAssetRepository, ISnapshotStore { public MongoAssetRepository(IMongoDatabase database) : base(database) @@ -27,16 +30,31 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets protected override string CollectionName() { - return "Projections_Assets"; + return "States_Assets"; } protected override Task SetupCollectionAsync(IMongoCollection collection) { return collection.Indexes.CreateOneAsync( - Index.Ascending(x => x.AppId) - .Ascending(x => x.FileName) - .Ascending(x => x.MimeType) - .Descending(x => x.LastModified)); + Index + .Ascending(x => x.State.AppId) + .Ascending(x => x.State.FileName) + .Ascending(x => x.State.MimeType) + .Descending(x => x.State.LastModified)); + } + + public async Task<(AssetState Value, long Version)> ReadAsync(Guid key) + { + var existing = + await Collection.Find(x => x.Id == key) + .FirstOrDefaultAsync(); + + if (existing != null) + { + return (existing.State, existing.Version); + } + + return (null, EtagVersion.NotFound); } public async Task> QueryAsync(Guid appId, HashSet mimeTypes = null, HashSet ids = null, string query = null, int take = 10, int skip = 0) @@ -44,7 +62,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets var filter = CreateFilter(appId, mimeTypes, ids, query); var assetEntities = - await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified) + await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.State.LastModified) .ToListAsync(); return assetEntities.OfType().ToList(); @@ -63,18 +81,16 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets public async Task FindAssetAsync(Guid id) { - var assetEntity = - await Collection.Find(s => s.Id == id) - .FirstOrDefaultAsync(); + var (state, etag) = await ReadAsync(id); - return assetEntity; + return state; } private static FilterDefinition CreateFilter(Guid appId, ICollection mimeTypes, ICollection ids, string query) { var filters = new List> { - Filter.Eq(x => x.AppId, appId) + Filter.Eq(x => x.State.AppId, appId) }; if (ids != null && ids.Count > 0) @@ -84,17 +100,47 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets if (mimeTypes != null && mimeTypes.Count > 0) { - filters.Add(Filter.In(x => x.MimeType, mimeTypes)); + filters.Add(Filter.In(x => x.State.MimeType, mimeTypes)); } if (!string.IsNullOrWhiteSpace(query)) { - filters.Add(Filter.Regex(x => x.FileName, new BsonRegularExpression(query, "i"))); + filters.Add(Filter.Regex(x => x.State.FileName, new BsonRegularExpression(query, "i"))); } var filter = Filter.And(filters); return filter; } + + public async Task WriteAsync(Guid key, AssetState value, long oldVersion, long newVersion) + { + try + { + await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, + Update + .Set(x => x.State, value) + .Set(x => x.Version, newVersion), + Upsert); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version) + .FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); + } + } + else + { + throw; + } + } + } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs similarity index 87% rename from src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsEntity.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs index 9fe49a6b0..cc7ddf110 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs @@ -9,9 +9,9 @@ using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Read.Assets; +using Squidex.Domain.Apps.Entities.Assets; -namespace Squidex.Domain.Apps.Read.MongoDb.Assets +namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { public sealed class MongoAssetStatsEntity : IAssetStatsEntity { @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets [BsonRequired] [BsonElement] - public Guid AppId { get; set; } + public Guid AssetId { get; set; } [BsonRequired] [BsonElement] diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs similarity index 73% rename from src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs index 290c754e7..ff01ab832 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs @@ -11,14 +11,15 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.Assets +namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { - public partial class MongoAssetStatsRepository : MongoRepositoryBase, IAssetStatsRepository, IAssetEventConsumer + public partial class MongoAssetStatsRepository : MongoRepositoryBase, IAssetStatsRepository, IEventConsumer { public MongoAssetStatsRepository(IMongoDatabase database) : base(database) @@ -30,17 +31,16 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets return "Projections_AssetStats"; } - protected override Task SetupCollectionAsync(IMongoCollection collection) + protected override async Task SetupCollectionAsync(IMongoCollection collection) { - return Task.WhenAll( - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.Date)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Date))); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Ascending(x => x.Date)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Date)); } public async Task> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate) { var originalSizesEntities = - await Collection.Find(x => x.AppId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date) + await Collection.Find(x => x.AssetId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date) .ToListAsync(); var enrichedSizes = new List(); @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets if (previousSize < 0) { var firstBeforeRangeEntity = - await Collection.Find(x => x.AppId == appId && x.Date < fromDate).SortByDescending(x => x.Date) + await Collection.Find(x => x.AssetId == appId && x.Date < fromDate).SortByDescending(x => x.Date) .FirstOrDefaultAsync(); previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L; @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets public async Task GetTotalSizeAsync(Guid appId) { var totalSizeEntity = - await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date) + await Collection.Find(x => x.AssetId == appId).SortByDescending(x => x.Date) .FirstOrDefaultAsync(); return totalSizeEntity?.TotalSize ?? 0; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs similarity index 90% rename from src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs index b46374dd7..16e153aa3 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs @@ -13,12 +13,10 @@ using Squidex.Domain.Apps.Events.Assets; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.MongoDb.Assets +namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { public partial class MongoAssetStatsRepository { - private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; - public string Name { get { return GetType().Name; } @@ -60,14 +58,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets if (assetStatsEntity == null) { var lastEntity = - await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date) + await Collection.Find(x => x.AssetId == appId).SortByDescending(x => x.Date) .FirstOrDefaultAsync(); assetStatsEntity = new MongoAssetStatsEntity { Id = id, Date = date, - AppId = appId, + AssetId = appId, TotalSize = lastEntity?.TotalSize ?? 0, TotalCount = lastEntity?.TotalCount ?? 0 }; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs similarity index 97% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs index be7dcb916..cb68d1c52 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs @@ -16,7 +16,7 @@ using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { public static class Extensions { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs similarity index 80% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index f2f19251e..f5127af9e 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -13,30 +13,23 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Contents; +using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { - public sealed class MongoContentEntity : - IContentEntity, - IUpdateableEntityWithVersion, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy, - IUpdateableEntityWithAppRef + public sealed class MongoContentEntity : IContentEntity { private NamedContentData data; [BsonId] [BsonElement] - [BsonRepresentation(BsonType.String)] - public Guid Id { get; set; } + public string DocumentId { get; set; } [BsonRequired] - [BsonElement("st")] - [BsonRepresentation(BsonType.String)] - public Status Status { get; set; } + [BsonElement] + public Guid Id { get; set; } [BsonRequired] [BsonElement("ct")] @@ -78,19 +71,29 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents [BsonElement("rd")] public List ReferencedIdsDeleted { get; set; } = new List(); + [BsonRequired] + [BsonElement("lt")] + public bool IsLatest { get; set; } + + [BsonRequired] + [BsonElement("st")] + [BsonRepresentation(BsonType.String)] + public Status Status { get; set; } + [BsonRequired] [BsonElement("do")] [BsonJson] - public IdContentData IdData { get; set; } + public IdContentData DataByIds { get; set; } - NamedContentData IContentEntity.Data + [BsonIgnore] + public NamedContentData Data { get { return data; } } public void ParseData(Schema schema) { - data = IdData.ToData(schema, ReferencedIdsDeleted); + data = DataByIds.ToData(schema, ReferencedIdsDeleted); } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs new file mode 100644 index 000000000..6643fac7a --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -0,0 +1,248 @@ +// ========================================================================== +// MongoContentRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.OData.UriParser; +using MongoDB.Driver; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.ConvertContent; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Contents.State; +using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents +{ + public partial class MongoContentRepository : MongoRepositoryBase, + IEventConsumer, + IContentRepository, + ISnapshotStore + { + private readonly IAppProvider appProvider; + + public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) + : base(database) + { + Guard.NotNull(appProvider, nameof(appProvider)); + + this.appProvider = appProvider; + } + + protected override string CollectionName() + { + return "States_Contents"; + } + + protected override async Task SetupCollectionAsync(IMongoCollection collection) + { + await collection.Indexes.CreateOneAsync( + Index + .Ascending(x => x.Id) + .Ascending(x => x.Version)); + + await collection.Indexes.CreateOneAsync( + Index + .Ascending(x => x.Id) + .Descending(x => x.Version)); + + await collection.Indexes.CreateOneAsync( + Index + .Ascending(x => x.SchemaId) + .Descending(x => x.IsLatest) + .Descending(x => x.LastModified)); + + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status)); + await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText)); + } + + public async Task WriteAsync(Guid key, ContentState value, long oldVersion, long newVersion) + { + var documentId = $"{key}_{newVersion}"; + + var schema = await appProvider.GetSchemaAsync(value.AppId, value.SchemaId, true); + + if (schema == null) + { + throw new InvalidOperationException($"Cannot find schema {value.SchemaId}"); + } + + var idData = value.Data?.ToIdModel(schema.SchemaDef, true); + + var document = SimpleMapper.Map(value, new MongoContentEntity + { + DocumentId = documentId, + DataText = idData?.ToFullText(), + DataByIds = idData, + IsLatest = true, + ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef), + }); + + try + { + await Collection.InsertOneAsync(document); + await Collection.UpdateManyAsync(x => x.Id == value.Id && x.Version < value.Version, Update.Set(x => x.IsLatest, false)); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == value.Id && x.IsLatest).Only(x => x.Id, x => x.Version) + .FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); + } + } + else + { + throw; + } + } + } + + public async Task<(ContentState Value, long Version)> ReadAsync(Guid key) + { + var contentEntity = + await Collection.Find(x => x.Id == key && x.IsLatest) + .FirstOrDefaultAsync(); + + if (contentEntity != null) + { + var schema = await appProvider.GetSchemaAsync(contentEntity.AppId, contentEntity.SchemaId, true); + + if (schema == null) + { + throw new InvalidOperationException($"Cannot find schema {contentEntity.SchemaId}"); + } + + contentEntity?.ParseData(schema.SchemaDef); + + return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); + } + + return (null, EtagVersion.NotFound); + } + + public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) + { + IFindFluent cursor; + try + { + cursor = + Collection + .Find(odataQuery, schema.Id, schema.SchemaDef, status) + .Take(odataQuery) + .Skip(odataQuery) + .Sort(odataQuery, schema.SchemaDef); + } + catch (NotSupportedException) + { + throw new ValidationException("This odata operation is not supported."); + } + catch (NotImplementedException) + { + throw new ValidationException("This odata operation is not supported."); + } + + var contentEntities = await cursor.ToListAsync(); + + foreach (var entity in contentEntities) + { + entity.ParseData(schema.SchemaDef); + } + + return contentEntities; + } + + public async Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) + { + IFindFluent cursor; + try + { + cursor = Collection.Find(odataQuery, schema.Id, schema.SchemaDef, status); + } + catch (NotSupportedException) + { + throw new ValidationException("This odata operation is not supported."); + } + catch (NotImplementedException) + { + throw new ValidationException("This odata operation is not supported."); + } + + return await cursor.CountAsync(); + } + + public async Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) + { + var contentsCount = + await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest) + .CountAsync(); + + return contentsCount; + } + + public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) + { + var contentEntities = + await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest) + .ToListAsync(); + + foreach (var entity in contentEntities) + { + entity.ParseData(schema.SchemaDef); + } + + return contentEntities.OfType().ToList(); + } + + public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList contentIds) + { + var contentEntities = + await Collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Only(x => x.Id) + .ToListAsync(); + + return contentIds.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList(); + } + + public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version) + { + var contentEntity = + await Collection.Find(x => x.Id == id && x.Version >= version).SortBy(x => x.Version) + .FirstOrDefaultAsync(); + + contentEntity?.ParseData(schema.SchemaDef); + + return contentEntity; + } + + public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) + { + var contentEntity = + await Collection.Find(x => x.Id == id && x.IsLatest) + .FirstOrDefaultAsync(); + + contentEntity?.ParseData(schema.SchemaDef); + + return contentEntity; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs new file mode 100644 index 000000000..74e8b36f7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs @@ -0,0 +1,58 @@ +// ========================================================================== +// MongoContentRepository_EventHandling.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Squidex.Domain.Apps.Events.Assets; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Tasks; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents +{ + public partial class MongoContentRepository + { + public string Name + { + get { return GetType().Name; } + } + + public string EventsFilter + { + get { return "^(content-)|(asset-)"; } + } + + public override Task ClearAsync() + { + return TaskHelper.Done; + } + + public Task On(Envelope @event) + { + return this.DispatchActionAsync(@event.Payload, @event.Headers); + } + + protected Task On(AssetDeleted @event) + { + return Collection.UpdateManyAsync( + Filter.And( + Filter.AnyEq(x => x.ReferencedIds, @event.AssetId), + Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.AssetId)), + Update.AddToSet(x => x.ReferencedIdsDeleted, @event.AssetId)); + } + + protected Task On(ContentDeleted @event) + { + return Collection.UpdateManyAsync( + Filter.And( + Filter.AnyEq(x => x.ReferencedIds, @event.ContentId), + Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.ContentId)), + Update.AddToSet(x => x.ReferencedIdsDeleted, @event.ContentId)); + } + } +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/ConstantVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/ConstantVisitor.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs index 9a21a44c4..58e116b23 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/ConstantVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs @@ -12,7 +12,7 @@ using Microsoft.OData.UriParser; using NodaTime; using NodaTime.Text; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public sealed class ConstantVisitor : QueryNodeVisitor { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterBuilder.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterBuilder.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs index 203ca26c7..895e5d79b 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs @@ -12,7 +12,7 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public static class FilterBuilder { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs similarity index 98% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterVisitor.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs index 250b4d513..f4d69dea9 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs @@ -13,7 +13,7 @@ using MongoDB.Bson; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public class FilterVisitor : QueryNodeVisitor> { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs index 1abdb16a0..0c09e1398 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs @@ -14,7 +14,7 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public static class FindExtensions { @@ -69,6 +69,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors var filters = new List> { Filter.Eq(x => x.SchemaId, schemaId), + Filter.Eq(x => x.IsLatest, true), Filter.In(x => x.Status, status) }; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs similarity index 97% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs index 36ddad63c..0052167fc 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs @@ -14,7 +14,7 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public sealed class PropertyVisitor : QueryNodeVisitor> { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs index a99f5af0e..3e106af60 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs @@ -9,7 +9,7 @@ using System; using Microsoft.OData.UriParser; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public class SearchTermVisitor : QueryNodeVisitor { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SortBuilder.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SortBuilder.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs index 6b851c528..627095331 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SortBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs @@ -11,7 +11,7 @@ using Microsoft.OData.UriParser; using MongoDB.Driver; using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors { public static class SortBuilder { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs index 4990fd528..a3223f276 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs @@ -12,11 +12,12 @@ using MongoDB.Bson.Serialization.Attributes; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.History +namespace Squidex.Domain.Apps.Entities.MongoDb.History { public sealed class MongoHistoryEventEntity : MongoEntity, IEntity, IEntityWithAppRef, + IUpdateableEntity, IUpdateableEntityWithVersion, IUpdateableEntityWithCreatedBy, IUpdateableEntityWithAppRef diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs similarity index 71% rename from src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs index 3404a64a7..2e876f414 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs @@ -11,13 +11,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.History; +using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Read.History; -using Squidex.Domain.Apps.Read.History.Repositories; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.History +namespace Squidex.Domain.Apps.Entities.MongoDb.History { public class MongoHistoryEventRepository : MongoRepositoryBase, IHistoryEventRepository, IEventConsumer { @@ -53,23 +53,22 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History return "Projections_History"; } - protected override Task SetupCollectionAsync(IMongoCollection collection) + protected override async Task SetupCollectionAsync(IMongoCollection collection) { - return Task.WhenAll( - collection.Indexes.CreateOneAsync( - Index - .Ascending(x => x.AppId) - .Ascending(x => x.Channel) - .Descending(x => x.Created) - .Descending(x => x.Version)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) })); + await collection.Indexes.CreateOneAsync( + Index + .Ascending(x => x.AppId) + .Ascending(x => x.Channel) + .Descending(x => x.Created) + .Descending(x => x.Version)); + + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) }); } public async Task> QueryByChannelAsync(Guid appId, string channelPrefix, int count) { var historyEventEntities = - await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix) - .SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count) + await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count) .ToListAsync(); return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList(); @@ -91,7 +90,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History entity.AppId = appEvent.AppId.Id; - entity.Version = @event.Headers.EventStreamNumber(); + if (@event.Headers.Contains(CommonHeaders.SnapshotVersion)) + { + entity.Version = @event.Headers.SnapshotVersion(); + } + else + { + entity.Version = @event.Headers.EventStreamNumber(); + } entity.Channel = message.Channel; entity.Message = message.Message; diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs index e3520bd43..c3aea8c1b 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs @@ -9,12 +9,12 @@ using System; using System.Collections.Generic; using NodaTime; -using Squidex.Domain.Apps.Read.History; +using Squidex.Domain.Apps.Entities.History; using Squidex.Infrastructure; #pragma warning disable RECS0029 // Warns about property or indexer setters and event adders or removers that do not use the value parameter -namespace Squidex.Domain.Apps.Read.MongoDb.History +namespace Squidex.Domain.Apps.Entities.MongoDb.History { internal sealed class ParsedHistoryEvent : IHistoryEventEntity { diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs index 3e840b8b1..b358f8511 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs @@ -14,13 +14,13 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb +namespace Squidex.Domain.Apps.Entities.MongoDb { public static class MongoCollectionExtensions { public static Task CreateAsync(this IMongoCollection collection, SquidexEvent @event, EnvelopeHeaders headers, Action updater) where T : class, IEntity, new() { - var entity = EntityMapper.Create(@event, headers, updater); + var entity = new T().Update(@event, headers, updater); return collection.InsertOneIfNotExistsAsync(entity); } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs new file mode 100644 index 000000000..1eeba5a13 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// MongoRuleEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Entities.Rules.State; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Rules +{ + public sealed class MongoRuleEntity + { + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; set; } + + [BsonElement] + [BsonRequired] + public int Version { get; set; } + + [BsonElement] + [BsonRequired] + public Guid AppId { get; set; } + + [BsonElement] + [BsonRequired] + public bool IsDeleted { get; set; } + + [BsonJson] + [BsonElement] + [BsonRequired] + public RuleState State { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs similarity index 90% rename from src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs index e48aa1543..f57382d6e 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs @@ -11,16 +11,16 @@ using MongoDB.Bson.Serialization.Attributes; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Entities.Rules; using Squidex.Infrastructure.MongoDb; -namespace Squidex.Domain.Apps.Read.MongoDb.Rules +namespace Squidex.Domain.Apps.Entities.MongoDb.Rules { public sealed class MongoRuleEventEntity : MongoEntity, IRuleEventEntity { [BsonRequired] [BsonElement] - public Guid AppId { get; set; } + public Guid AssetId { get; set; } [BsonRequired] [BsonElement] diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs similarity index 70% rename from src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs rename to src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs index 99b22d125..73c5655f2 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs @@ -14,12 +14,12 @@ using MongoDB.Driver; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Read.MongoDb.Rules +namespace Squidex.Domain.Apps.Entities.MongoDb.Rules { public sealed class MongoRuleEventRepository : MongoRepositoryBase, IRuleEventRepository { @@ -33,12 +33,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules return "RuleEvents"; } - protected override Task SetupCollectionAsync(IMongoCollection collection) + protected override async Task SetupCollectionAsync(IMongoCollection collection) { - return Task.WhenAll( - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Created)), - collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero })); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Created)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }); } public Task QueryPendingAsync(Instant now, Func callback, CancellationToken cancellationToken = default(CancellationToken)) @@ -49,7 +48,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules public async Task> QueryByAppAsync(Guid appId, int skip = 0, int take = 20) { var ruleEventEntities = - await Collection.Find(x => x.AppId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created) + await Collection.Find(x => x.AssetId == appId).Skip(skip).Limit(take).SortByDescending(x => x.Created) .ToListAsync(); return ruleEventEntities; @@ -66,7 +65,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules public async Task CountByAppAsync(Guid appId) { - return (int)await Collection.CountAsync(x => x.AppId == appId); + return (int)await Collection.CountAsync(x => x.AssetId == appId); } public Task EnqueueAsync(Guid id, Instant nextAttempt) @@ -84,11 +83,12 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextAttempt) { return Collection.UpdateOneAsync(x => x.Id == jobId, - Update.Set(x => x.Result, result) - .Set(x => x.LastDump, dump) - .Set(x => x.JobResult, jobResult) - .Set(x => x.NextAttempt, nextAttempt) - .Inc(x => x.NumCalls, 1)); + Update + .Set(x => x.Result, result) + .Set(x => x.LastDump, dump) + .Set(x => x.JobResult, jobResult) + .Set(x => x.NextAttempt, nextAttempt) + .Inc(x => x.NumCalls, 1)); } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs new file mode 100644 index 000000000..ff579d0f3 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs @@ -0,0 +1,95 @@ +// ========================================================================== +// MongoRuleRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Rules.State; +using Squidex.Infrastructure; +using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Rules +{ + public sealed class MongoRuleRepository : MongoRepositoryBase, IRuleRepository, ISnapshotStore + { + public MongoRuleRepository(IMongoDatabase database) + : base(database) + { + } + + protected override string CollectionName() + { + return "States_Rules"; + } + + protected override async Task SetupCollectionAsync(IMongoCollection collection) + { + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.IsDeleted)); + } + + public async Task<(RuleState Value, long Version)> ReadAsync(Guid key) + { + var existing = + await Collection.Find(x => x.Id == key) + .FirstOrDefaultAsync(); + + if (existing != null) + { + return (existing.State, existing.Version); + } + + return (null, EtagVersion.NotFound); + } + + public async Task> QueryRuleIdsAsync(Guid appId) + { + var ruleEntities = + await Collection.Find(x => x.AppId == appId && !x.IsDeleted).Only(x => x.Id) + .ToListAsync(); + + return ruleEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); + } + + public async Task WriteAsync(Guid key, RuleState value, long oldVersion, long newVersion) + { + try + { + await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, + Update + .Set(x => x.State, value) + .Set(x => x.AppId, value.AppId) + .Set(x => x.IsDeleted, value.IsDeleted) + .Set(x => x.Version, newVersion), + Upsert); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == key).Only(x => x.Id, x => x.Version) + .FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); + } + } + else + { + throw; + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs new file mode 100644 index 000000000..a6b75820c --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// MongoSchemaEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Squidex.Domain.Apps.Entities.Schemas.State; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas +{ + public sealed class MongoSchemaEntity + { + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + public Guid Id { get; set; } + + [BsonElement] + [BsonRequired] + public string Name { get; set; } + + [BsonElement] + [BsonRequired] + public int Version { get; set; } + + [BsonElement] + [BsonRequired] + public Guid AppId { get; set; } + + [BsonJson] + [BsonElement] + [BsonRequired] + public SchemaState State { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs new file mode 100644 index 000000000..9da25e5e7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs @@ -0,0 +1,104 @@ +// ========================================================================== +// MongoSchemaRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Schemas.Repositories; +using Squidex.Domain.Apps.Entities.Schemas.State; +using Squidex.Infrastructure; +using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Schemas +{ + public sealed class MongoSchemaRepository : MongoRepositoryBase, ISchemaRepository, ISnapshotStore + { + public MongoSchemaRepository(IMongoDatabase database) + : base(database) + { + } + + protected override string CollectionName() + { + return "States_Schemas"; + } + + protected override async Task SetupCollectionAsync(IMongoCollection collection) + { + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)); + await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)); + } + + public async Task<(SchemaState Value, long Version)> ReadAsync(Guid key) + { + var existing = + await Collection.Find(x => x.Id == key) + .FirstOrDefaultAsync(); + + if (existing != null) + { + return (existing.State, existing.Version); + } + + return (null, EtagVersion.NotFound); + } + + public async Task FindSchemaIdAsync(Guid appId, string name) + { + var schemaEntity = + await Collection.Find(x => x.Name == name).Only(x => x.Id) + .FirstOrDefaultAsync(); + + return schemaEntity != null ? Guid.Parse(schemaEntity["_id"].AsString) : Guid.Empty; + } + + public async Task> QuerySchemaIdsAsync(Guid appId) + { + var schemaEntities = + await Collection.Find(x => x.AppId == appId).Only(x => x.Id) + .ToListAsync(); + + return schemaEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); + } + + public async Task WriteAsync(Guid key, SchemaState value, long oldVersion, long newVersion) + { + try + { + await Collection.UpdateOneAsync(x => x.Id == key && x.Version == oldVersion, + Update + .Set(x => x.State, value) + .Set(x => x.AppId, value.AppId) + .Set(x => x.Name, value.Name) + .Set(x => x.Version, newVersion), + Upsert); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + var existingVersion = + await Collection.Find(x => x.Id == key).Only(x => x.Version) + .FirstOrDefaultAsync(); + + if (existingVersion != null) + { + throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); + } + } + else + { + throw; + } + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj similarity index 92% rename from src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj rename to src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj index b8e390b69..2fc0d6ace 100644 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Squidex.Domain.Apps.Write/AppAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/AppAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs index cc46b1dbf..1bd5d3a75 100644 --- a/src/Squidex.Domain.Apps.Write/AppAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write +namespace Squidex.Domain.Apps.Entities { public class AppAggregateCommand : AppCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/AppCommand.cs b/src/Squidex.Domain.Apps.Entities/AppCommand.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/AppCommand.cs rename to src/Squidex.Domain.Apps.Entities/AppCommand.cs index a948421a5..9ab07df33 100644 --- a/src/Squidex.Domain.Apps.Write/AppCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/AppCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write +namespace Squidex.Domain.Apps.Entities { public abstract class AppCommand : SquidexCommand { diff --git a/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/src/Squidex.Domain.Apps.Entities/AppProvider.cs new file mode 100644 index 000000000..e6772c77b --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -0,0 +1,150 @@ +// ========================================================================== +// AppProvider.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Apps.Repositories; +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.Infrastructure; +using Squidex.Infrastructure.States; + +namespace Squidex.Domain.Apps.Entities +{ + public sealed class AppProvider : IAppProvider + { + private readonly IAppRepository appRepository; + private readonly IRuleRepository ruleRepository; + private readonly ISchemaRepository schemaRepository; + private readonly IStateFactory stateFactory; + + public AppProvider( + IAppRepository appRepository, + ISchemaRepository schemaRepository, + IStateFactory stateFactory, + IRuleRepository ruleRepository) + { + Guard.NotNull(appRepository, nameof(appRepository)); + Guard.NotNull(schemaRepository, nameof(schemaRepository)); + Guard.NotNull(stateFactory, nameof(stateFactory)); + Guard.NotNull(ruleRepository, nameof(ruleRepository)); + + this.appRepository = appRepository; + this.schemaRepository = schemaRepository; + this.stateFactory = stateFactory; + this.ruleRepository = ruleRepository; + } + + public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id) + { + var app = await stateFactory.GetSingleAsync(appId); + + if (IsNotFound(app)) + { + return (null, null); + } + + var schema = await stateFactory.GetSingleAsync(id); + + return IsNotFound(false, schema) ? (null, null) : (app.State, schema.State); + } + + public async Task GetAppAsync(string appName) + { + var appId = await GetAppIdAsync(appName); + + if (appId == Guid.Empty) + { + return null; + } + + var app = await stateFactory.GetSingleAsync(appId); + + return IsNotFound(app) ? null : app.State; + } + + public async Task GetSchemaAsync(Guid appId, string name, bool provideDeleted = false) + { + var schemaId = await GetSchemaIdAsync(appId, name); + + if (schemaId == Guid.Empty) + { + return null; + } + + var schema = await stateFactory.GetSingleAsync(schemaId); + + return IsNotFound(provideDeleted, schema) ? null : schema.State; + } + + public async Task GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false) + { + var schema = await stateFactory.GetSingleAsync(id); + + return IsNotFound(provideDeleted, schema) ? null : schema.State; + } + + public async Task> GetSchemasAsync(Guid appId) + { + var ids = await schemaRepository.QuerySchemaIdsAsync(appId); + + var schemas = + await Task.WhenAll( + ids.Select(id => stateFactory.GetSingleAsync(id))); + + return schemas.Select(a => (ISchemaEntity)a.State).ToList(); + } + + public async Task> GetRulesAsync(Guid appId) + { + var ids = await ruleRepository.QueryRuleIdsAsync(appId); + + var rules = + await Task.WhenAll( + ids.Select(id => stateFactory.GetSingleAsync(id))); + + return rules.Select(a => (IRuleEntity)a.State).ToList(); + } + + public async Task> GetUserApps(string userId) + { + var ids = await appRepository.QueryUserAppIdsAsync(userId); + + var apps = + await Task.WhenAll( + ids.Select(id => stateFactory.GetSingleAsync(id))); + + return apps.Select(a => (IAppEntity)a.State).ToList(); + } + + private Task GetAppIdAsync(string name) + { + return appRepository.FindAppIdByNameAsync(name); + } + + private Task GetSchemaIdAsync(Guid appId, string name) + { + return schemaRepository.FindSchemaIdAsync(appId, name); + } + + private static bool IsNotFound(AppDomainObject app) + { + return app.Version < 0; + } + + private static bool IsNotFound(bool provideDeleted, SchemaDomainObject schema) + { + return schema.Version < 0 || (schema.State.IsDeleted && !provideDeleted); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs similarity index 66% rename from src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs rename to src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs index 7c3978cc7..5a3a7f1d3 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs @@ -8,16 +8,15 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; -using Squidex.Domain.Apps.Write.Apps.Guards; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Guards; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Dispatching; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Write.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class AppCommandMiddleware : ICommandMiddleware { @@ -47,23 +46,23 @@ namespace Squidex.Domain.Apps.Write.Apps this.appPlansBillingManager = appPlansBillingManager; } - protected async Task On(CreateApp command, CommandContext context) + protected Task On(CreateApp command, CommandContext context) { - await handler.CreateAsync(context, async a => + return handler.CreateSyncedAsync(context, async a => { await GuardApp.CanCreate(command, appProvider); a.Create(command); - context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); + context.Complete(EntityCreatedResult.Create(command.AppId, a.Version)); }); } - protected async Task On(AssignContributor command, CommandContext context) + protected Task On(AssignContributor command, CommandContext context) { - await handler.UpdateAsync(context, async a => + return handler.UpdateSyncedAsync(context, async a => { - await GuardAppContributors.CanAssign(a.Contributors, command, userResolver, appPlansProvider.GetPlan(a.Plan?.PlanId)); + await GuardAppContributors.CanAssign(a.State.Contributors, command, userResolver, appPlansProvider.GetPlan(a.State.Plan?.PlanId)); a.AssignContributor(command); }); @@ -71,9 +70,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(RemoveContributor command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppContributors.CanRemove(a.Contributors, command); + GuardAppContributors.CanRemove(a.State.Contributors, command); a.RemoveContributor(command); }); @@ -81,9 +80,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(AttachClient command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppClients.CanAttach(a.Clients, command); + GuardAppClients.CanAttach(a.State.Clients, command); a.AttachClient(command); }); @@ -91,9 +90,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(UpdateClient command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppClients.CanUpdate(a.Clients, command); + GuardAppClients.CanUpdate(a.State.Clients, command); a.UpdateClient(command); }); @@ -101,9 +100,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(RevokeClient command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppClients.CanRevoke(a.Clients, command); + GuardAppClients.CanRevoke(a.State.Clients, command); a.RevokeClient(command); }); @@ -111,9 +110,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(AddLanguage command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppLanguages.CanAdd(a.LanguagesConfig, command); + GuardAppLanguages.CanAdd(a.State.LanguagesConfig, command); a.AddLanguage(command); }); @@ -121,9 +120,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(RemoveLanguage command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppLanguages.CanRemove(a.LanguagesConfig, command); + GuardAppLanguages.CanRemove(a.State.LanguagesConfig, command); a.RemoveLanguage(command); }); @@ -131,9 +130,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(UpdateLanguage command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAppLanguages.CanUpdate(a.LanguagesConfig, command); + GuardAppLanguages.CanUpdate(a.State.LanguagesConfig, command); a.UpdateLanguage(command); }); @@ -141,9 +140,9 @@ namespace Squidex.Domain.Apps.Write.Apps protected Task On(ChangePlan command, CommandContext context) { - return handler.UpdateAsync(context, async a => + return handler.UpdateSyncedAsync(context, async a => { - GuardApp.CanChangePlan(command, a.Plan, appPlansProvider); + GuardApp.CanChangePlan(command, a.State.Plan, appPlansProvider); if (command.FromCallback) { @@ -151,7 +150,7 @@ namespace Squidex.Domain.Apps.Write.Apps } else { - var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, a.Id, a.Name, command.PlanId); + var result = await appPlansBillingManager.ChangePlanAsync(command.Actor.Identifier, command.AppId.Id, a.State.Name, command.PlanId); if (result is PlanChangedResult) { diff --git a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs similarity index 60% rename from src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs index 6c2a7b7bb..d11519917 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs @@ -8,130 +8,41 @@ using System; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.State; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Events.Apps.Utils; -using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Write.Apps +namespace Squidex.Domain.Apps.Entities.Apps { - public class AppDomainObject : DomainObjectBase + public sealed class AppDomainObject : DomainObjectBase { - private AppContributors contributors = AppContributors.Empty; - private AppClients clients = AppClients.Empty; - private LanguagesConfig languagesConfig = LanguagesConfig.English; - private AppPlan plan; - private string name; - - public string Name - { - get { return name; } - } - - public AppPlan Plan - { - get { return plan; } - } - - public AppClients Clients - { - get { return clients; } - } - - public AppContributors Contributors - { - get { return contributors; } - } - - public LanguagesConfig LanguagesConfig - { - get { return languagesConfig; } - } - - public AppDomainObject(Guid id, int version) - : base(id, version) - { - } - - protected void On(AppCreated @event) - { - name = @event.Name; - } - - protected void On(AppContributorAssigned @event) - { - contributors = contributors.Apply(@event); - } - - protected void On(AppContributorRemoved @event) - { - contributors = contributors.Apply(@event); - } - - protected void On(AppClientAttached @event) - { - clients = clients.Apply(@event); - } - - protected void On(AppClientUpdated @event) - { - clients = clients.Apply(@event); - } - - protected void On(AppClientRenamed @event) - { - clients = clients.Apply(@event); - } - - protected void On(AppClientRevoked @event) - { - clients = clients.Apply(@event); - } - - protected void On(AppLanguageAdded @event) - { - languagesConfig = languagesConfig.Apply(@event); - } - - protected void On(AppLanguageRemoved @event) - { - languagesConfig = languagesConfig.Apply(@event); - } - - protected void On(AppLanguageUpdated @event) - { - languagesConfig = languagesConfig.Apply(@event); - } - - protected void On(AppPlanChanged @event) - { - plan = string.IsNullOrWhiteSpace(@event.PlanId) ? null : new AppPlan(@event.Actor, @event.PlanId); - } - - protected override void DispatchEvent(Envelope @event) - { - this.DispatchAction(@event.Payload); - } - public AppDomainObject Create(CreateApp command) { ThrowIfCreated(); var appId = new NamedId(command.AppId, command.Name); - RaiseEvent(SimpleMapper.Map(command, new AppCreated { AppId = appId })); - + RaiseEvent(SimpleMapper.Map(command, CreateInitalEvent(appId))); RaiseEvent(SimpleMapper.Map(command, CreateInitialOwner(appId, command))); RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId))); return this; } + public AppDomainObject UpdateLanguage(UpdateLanguage command) + { + ThrowIfNotCreated(); + + RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); + + return this; + } + public AppDomainObject UpdateClient(UpdateClient command) { ThrowIfNotCreated(); @@ -203,15 +114,6 @@ namespace Squidex.Domain.Apps.Write.Apps return this; } - public AppDomainObject UpdateLanguage(UpdateLanguage command) - { - ThrowIfNotCreated(); - - RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated())); - - return this; - } - public AppDomainObject ChangePlan(ChangePlan command) { ThrowIfNotCreated(); @@ -225,12 +127,17 @@ namespace Squidex.Domain.Apps.Write.Apps { if (@event.AppId == null) { - @event.AppId = new NamedId(Id, name); + @event.AppId = new NamedId(State.Id, State.Name); } RaiseEvent(Envelope.Create(@event)); } + private static AppCreated CreateInitalEvent(NamedId appId) + { + return new AppCreated { AppId = appId }; + } + private static AppLanguageAdded CreateInitialLanguage(NamedId id) { return new AppLanguageAdded { AppId = id, Language = Language.EN }; @@ -243,7 +150,7 @@ namespace Squidex.Domain.Apps.Write.Apps private void ThrowIfNotCreated() { - if (string.IsNullOrWhiteSpace(name)) + if (string.IsNullOrWhiteSpace(State.Name)) { throw new DomainException("App has not been created."); } @@ -251,10 +158,15 @@ namespace Squidex.Domain.Apps.Write.Apps private void ThrowIfCreated() { - if (!string.IsNullOrWhiteSpace(name)) + if (!string.IsNullOrWhiteSpace(State.Name)) { throw new DomainException("App has already been created."); } } + + protected override void OnRaised(Envelope @event) + { + UpdateState(State.Apply(@event)); + } } } diff --git a/src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs rename to src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs index 39fe7824b..1b9013944 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public static class AppEntityExtensions { diff --git a/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs rename to src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs index 2e677ba59..635387de5 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs @@ -7,13 +7,13 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Read.History; using Squidex.Infrastructure; using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class AppHistoryEventsCreator : HistoryEventsCreatorBase { @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Read.Apps "changed master language to {[Language]}"); } - protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers) + protected Task On(AppContributorRemoved @event) { const string channel = "settings.contributors"; @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Contributor", @event.ContributorId)); } - protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers) + protected Task On(AppContributorAssigned @event) { const string channel = "settings.contributors"; @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Contributor", @event.ContributorId).AddParameter("Permission", @event.Permission)); } - protected Task On(AppClientAttached @event, EnvelopeHeaders headers) + protected Task On(AppClientAttached @event) { const string channel = "settings.clients"; @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Id", @event.Id)); } - protected Task On(AppClientRevoked @event, EnvelopeHeaders headers) + protected Task On(AppClientRevoked @event) { const string channel = "settings.clients"; @@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Id", @event.Id)); } - protected Task On(AppClientRenamed @event, EnvelopeHeaders headers) + protected Task On(AppClientRenamed @event) { const string channel = "settings.clients"; @@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Id", @event.Id).AddParameter("Name", ClientName(@event))); } - protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers) + protected Task On(AppLanguageAdded @event) { const string channel = "settings.languages"; @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Language", @event.Language)); } - protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers) + protected Task On(AppLanguageRemoved @event) { const string channel = "settings.languages"; @@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Language", @event.Language)); } - protected Task On(AppLanguageUpdated @event, EnvelopeHeaders headers) + protected Task On(AppLanguageUpdated @event) { const string channel = "settings.languages"; @@ -123,7 +123,7 @@ namespace Squidex.Domain.Apps.Read.Apps .AddParameter("Language", @event.Language)); } - protected Task On(AppMasterLanguageSet @event, EnvelopeHeaders headers) + protected Task On(AppMasterLanguageSet @event) { const string channel = "settings.languages"; diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs index 0d6f3396e..486f8d0aa 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class AddLanguage : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs similarity index 91% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs index 361ee348f..0dad40b86 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Apps; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class AssignContributor : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs similarity index 91% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs index f69cfb384..2cf0bdf39 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class AttachClient : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs index 4d84ee36c..eab67f42e 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class ChangePlan : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs index 9ef163160..eb173e4f5 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class CreateApp : SquidexCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs index c579247a3..5bda9002b 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class RemoveContributor : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs index 8a08d3c93..1e7fbf499 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class RemoveLanguage : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs index 68abb555e..cf48c425f 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class RevokeClient : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs index 5cbe2496e..82059fbcd 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs @@ -1,5 +1,5 @@ // ========================================================================== -// RenameClient.cs +// UpdateClient.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Apps; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class UpdateClient : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs index 22092874f..d8badb558 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Commands +namespace Squidex.Domain.Apps.Entities.Apps.Commands { public sealed class UpdateLanguage : AppAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs index 89540ff13..ced905013 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs @@ -9,12 +9,11 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardApp { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs similarity index 97% rename from src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index 3a19fe5ea..1d48aa8d4 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -7,10 +7,10 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppClients { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs similarity index 94% rename from src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index 64ecb94f1..bd0304d22 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -1,5 +1,5 @@ // ========================================================================== -// GuardApp.cs +// GuardAppContributors.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,12 +9,12 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; using Squidex.Shared.Users; -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppContributors { diff --git a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs similarity index 96% rename from src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index 1ac39c4b2..a31fa3413 100644 --- a/src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -1,5 +1,5 @@ // ========================================================================== -// GuardApp.cs +// GuardAppLanguages.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -7,10 +7,10 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public static class GuardAppLanguages { diff --git a/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs similarity index 81% rename from src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs rename to src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs index df1743d69..154478f1e 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs @@ -8,17 +8,13 @@ using Squidex.Domain.Apps.Core.Apps; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public interface IAppEntity : IEntity, IEntityWithVersion { - string Etag { get; } - string Name { get; } - string PlanId { get; } - - string PlanOwner { get; } + AppPlan Plan { get; } AppClients Clients { get; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs b/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs new file mode 100644 index 000000000..208ff3d5d --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// IAppRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squidex.Domain.Apps.Entities.Apps.Repositories +{ + public interface IAppRepository + { + Task FindAppIdByNameAsync(string name); + + Task> QueryUserAppIdsAsync(string userId); + } +} diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs index 3bc0a0012..623d71f26 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public interface IAppLimitsPlan { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlanBillingManager.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs index ebb189b2e..d0c601d06 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlanBillingManager.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs @@ -9,7 +9,7 @@ using System; using System.Threading.Tasks; -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public interface IAppPlanBillingManager { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlansProvider.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlansProvider.cs index 5128fdfb2..53dea5201 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlansProvider.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public interface IAppPlansProvider { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/IChangePlanResult.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IChangePlanResult.cs similarity index 88% rename from src/Squidex.Domain.Apps.Read/Apps/Services/IChangePlanResult.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/IChangePlanResult.cs index 5086dff24..c8cde7963 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/IChangePlanResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IChangePlanResult.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public interface IChangePlanResult { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs index 0ed64ddcb..165c11bd2 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations +namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations { public sealed class ConfigAppLimitsPlan : IAppLimitsPlan { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppPlansProvider.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppPlansProvider.cs index ed2e6181a..d27f8c189 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppPlansProvider.cs @@ -1,5 +1,5 @@ // ========================================================================== -// ConfigAppLimitsProvider.cs +// ConfigAppPlansProvider.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -11,7 +11,7 @@ using System.Collections.Generic; using System.Linq; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations +namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations { public sealed class ConfigAppPlansProvider : IAppPlansProvider { @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations { Guard.NotNull(app, nameof(app)); - return GetPlan(app.PlanId); + return GetPlan(app.Plan?.PlanId); } public IAppLimitsPlan GetPlan(string planId) @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations { Guard.NotNull(app, nameof(app)); - return GetPlanUpgrade(app.PlanId); + return GetPlanUpgrade(app.Plan?.PlanId); } public IAppLimitsPlan GetPlanUpgrade(string planId) diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/NoopAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/NoopAppPlanBillingManager.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs index 5f6bdec01..6a053c294 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/NoopAppPlanBillingManager.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs @@ -9,7 +9,7 @@ using System; using System.Threading.Tasks; -namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations +namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations { public sealed class NoopAppPlanBillingManager : IAppPlanBillingManager { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangeAsyncResult.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangeAsyncResult.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs index 3a4ec4f9e..6c0c15865 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangeAsyncResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs @@ -5,7 +5,8 @@ // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Apps.Services + +namespace Squidex.Domain.Apps.Entities.Apps.Services { public sealed class PlanChangeAsyncResult : IChangePlanResult { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangedResult.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangedResult.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangedResult.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangedResult.cs index 1456823c5..efcc95b3d 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangedResult.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public sealed class PlanChangedResult : IChangePlanResult { diff --git a/src/Squidex.Domain.Apps.Read/Apps/Services/RedirectToCheckoutResult.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/RedirectToCheckoutResult.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Apps/Services/RedirectToCheckoutResult.cs rename to src/Squidex.Domain.Apps.Entities/Apps/Services/RedirectToCheckoutResult.cs index 21b3043d1..5d1d2a441 100644 --- a/src/Squidex.Domain.Apps.Read/Apps/Services/RedirectToCheckoutResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/RedirectToCheckoutResult.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Apps.Services +namespace Squidex.Domain.Apps.Entities.Apps.Services { public sealed class RedirectToCheckoutResult : IChangePlanResult { diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs new file mode 100644 index 000000000..35ed457a5 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -0,0 +1,107 @@ +// ========================================================================== +// AppState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Apps; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Entities.Apps.State +{ + public class AppState : DomainObjectState, + IAppEntity + { + private static readonly LanguagesConfig English = LanguagesConfig.Build(Language.EN); + + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public AppPlan Plan { get; set; } + + [JsonProperty] + public AppClients Clients { get; set; } = AppClients.Empty; + + [JsonProperty] + public AppContributors Contributors { get; set; } = AppContributors.Empty; + + [JsonProperty] + public LanguagesConfig LanguagesConfig { get; set; } = English; + + protected void On(AppCreated @event) + { + SimpleMapper.Map(@event, this); + } + + protected void On(AppPlanChanged @event) + { + Plan = @event.PlanId == null ? null : new AppPlan(@event.Actor, @event.PlanId); + } + + protected void On(AppContributorAssigned @event) + { + Contributors = Contributors.Assign(@event.ContributorId, @event.Permission); + } + + protected void On(AppContributorRemoved @event) + { + Contributors = Contributors.Remove(@event.ContributorId); + } + + protected void On(AppClientAttached @event) + { + Clients = Clients.Add(@event.Id, @event.Secret); + } + + protected void On(AppClientUpdated @event) + { + Clients = Clients.Update(@event.Id, @event.Permission); + } + + protected void On(AppClientRenamed @event) + { + Clients = Clients.Rename(@event.Id, @event.Name); + } + + protected void On(AppClientRevoked @event) + { + Clients = Clients.Revoke(@event.Id); + } + + protected void On(AppLanguageAdded @event) + { + LanguagesConfig = LanguagesConfig.Set(new LanguageConfig(@event.Language)); + } + + protected void On(AppLanguageRemoved @event) + { + LanguagesConfig = LanguagesConfig.Remove(@event.Language); + } + + protected void On(AppLanguageUpdated @event) + { + LanguagesConfig = LanguagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, @event.Fallback)); + + if (@event.IsMaster) + { + LanguagesConfig = LanguagesConfig.MakeMaster(@event.Language); + } + } + + public AppState Apply(Envelope @event) + { + var payload = (SquidexEvent)@event.Payload; + + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs similarity index 81% rename from src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs rename to src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index de46cceda..dcc8a3029 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs @@ -8,14 +8,14 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Write.Assets.Commands; -using Squidex.Domain.Apps.Write.Assets.Guards; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Guards; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Dispatching; -namespace Squidex.Domain.Apps.Write.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public class AssetCommandMiddleware : ICommandMiddleware { @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Write.Assets command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); try { - var asset = await handler.CreateAsync(context, async a => + var asset = await handler.CreateSyncedAsync(context, async a => { GuardAsset.CanCreate(command); @@ -50,10 +50,10 @@ namespace Squidex.Domain.Apps.Write.Assets await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); - context.Complete(EntityCreatedResult.Create(a.Id, a.Version)); + context.Complete(EntityCreatedResult.Create(command.AssetId, a.Version)); }); - await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), asset.Id.ToString(), asset.FileVersion, null); + await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), command.AssetId.ToString(), asset.State.FileVersion, null); } finally { @@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Write.Assets try { - var asset = await handler.UpdateAsync(context, async a => + var asset = await handler.UpdateSyncedAsync(context, async a => { GuardAsset.CanUpdate(command); @@ -75,10 +75,10 @@ namespace Squidex.Domain.Apps.Write.Assets await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); - context.Complete(new AssetSavedResult(a.Version, a.FileVersion)); + context.Complete(new AssetSavedResult(a.Version, a.State.FileVersion)); }); - await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), asset.Id.ToString(), asset.FileVersion, null); + await assetStore.CopyTemporaryAsync(context.ContextId.ToString(), command.AssetId.ToString(), asset.State.FileVersion, null); } finally { @@ -88,9 +88,9 @@ namespace Squidex.Domain.Apps.Write.Assets protected Task On(RenameAsset command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { - GuardAsset.CanRename(command, a.FileName); + GuardAsset.CanRename(command, a.State.FileName); a.Rename(command); }); @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Write.Assets protected Task On(DeleteAsset command, CommandContext context) { - return handler.UpdateAsync(context, a => + return handler.UpdateSyncedAsync(context, a => { GuardAsset.CanDelete(command); diff --git a/src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs similarity index 60% rename from src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs index 6fa06c5a7..ada2fab99 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs @@ -6,69 +6,18 @@ // All rights reserved. // ========================================================================== -using System; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.State; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Domain.Apps.Write.Assets.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Write.Assets +namespace Squidex.Domain.Apps.Entities.Assets { - public class AssetDomainObject : DomainObjectBase + public sealed class AssetDomainObject : DomainObjectBase { - private bool isDeleted; - private long fileVersion = -1; - private long totalSize; - private string fileName; - - public bool IsDeleted - { - get { return isDeleted; } - } - - public long FileVersion - { - get { return fileVersion; } - } - - public string FileName - { - get { return fileName; } - } - - public AssetDomainObject(Guid id, int version) - : base(id, version) - { - } - - protected void On(AssetCreated @event) - { - fileVersion = @event.FileVersion; - fileName = @event.FileName; - - totalSize += @event.FileSize; - } - - protected void On(AssetUpdated @event) - { - fileVersion = @event.FileVersion; - - totalSize += @event.FileSize; - } - - protected void On(AssetRenamed @event) - { - fileName = @event.FileName; - } - - protected void On(AssetDeleted @event) - { - isDeleted = true; - } - public AssetDomainObject Create(CreateAsset command) { VerifyNotCreated(); @@ -77,7 +26,7 @@ namespace Squidex.Domain.Apps.Write.Assets { FileName = command.File.FileName, FileSize = command.File.FileSize, - FileVersion = fileVersion + 1, + FileVersion = 0, MimeType = command.File.MimeType, PixelWidth = command.ImageInfo?.PixelWidth, PixelHeight = command.ImageInfo?.PixelHeight, @@ -95,7 +44,7 @@ namespace Squidex.Domain.Apps.Write.Assets var @event = SimpleMapper.Map(command, new AssetUpdated { - FileVersion = fileVersion + 1, + FileVersion = State.FileVersion + 1, FileSize = command.File.FileSize, MimeType = command.File.MimeType, PixelWidth = command.ImageInfo?.PixelWidth, @@ -112,7 +61,7 @@ namespace Squidex.Domain.Apps.Write.Assets { VerifyCreatedAndNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = totalSize })); + RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = State.TotalSize })); return this; } @@ -128,7 +77,7 @@ namespace Squidex.Domain.Apps.Write.Assets private void VerifyNotCreated() { - if (!string.IsNullOrWhiteSpace(fileName)) + if (!string.IsNullOrWhiteSpace(State.FileName)) { throw new DomainException("Asset has already been created."); } @@ -136,15 +85,15 @@ namespace Squidex.Domain.Apps.Write.Assets private void VerifyCreatedAndNotDeleted() { - if (isDeleted || string.IsNullOrWhiteSpace(fileName)) + if (State.IsDeleted || string.IsNullOrWhiteSpace(State.FileName)) { throw new DomainException("Asset has already been deleted or not created yet."); } } - protected override void DispatchEvent(Envelope @event) + protected override void OnRaised(Envelope @event) { - this.DispatchAction(@event.Payload); + UpdateState(State.Apply(@event)); } } } diff --git a/src/Squidex.Domain.Apps.Write/Assets/AssetSavedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/Assets/AssetSavedResult.cs rename to src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs index 5d38142c6..129bc8379 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/AssetSavedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public class AssetSavedResult : EntitySavedResult { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/AssetAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Assets/Commands/AssetAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs index b62d9dee9..001cf574b 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/AssetAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Assets.Commands +namespace Squidex.Domain.Apps.Entities.Assets.Commands { public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/CreateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Assets/Commands/CreateAsset.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs index 7e11b36e6..7ac46f525 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/CreateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Assets; -namespace Squidex.Domain.Apps.Write.Assets.Commands +namespace Squidex.Domain.Apps.Entities.Assets.Commands { public sealed class CreateAsset : AssetAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs index a9cfd5cfe..1dca53261 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Assets.Commands +namespace Squidex.Domain.Apps.Entities.Assets.Commands { public sealed class DeleteAsset : AssetAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs index 493acc979..bc5a278f8 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Assets.Commands +namespace Squidex.Domain.Apps.Entities.Assets.Commands { public sealed class RenameAsset : AssetAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Commands/UpdateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Assets/Commands/UpdateAsset.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs index 32b8b3935..750e0641c 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Commands/UpdateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure.Assets; -namespace Squidex.Domain.Apps.Write.Assets.Commands +namespace Squidex.Domain.Apps.Entities.Assets.Commands { public sealed class UpdateAsset : AssetAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs index 1e3a7a57d..2ac0d086d 100644 --- a/src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs @@ -6,10 +6,10 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Write.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Assets.Guards +namespace Squidex.Domain.Apps.Entities.Assets.Guards { public static class GuardAsset { diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs rename to src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs index 9a502ed7b..8563d3931 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.ValidateContent; -namespace Squidex.Domain.Apps.Read.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetEntity : IEntity, diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetStatsEntity.cs b/src/Squidex.Domain.Apps.Entities/Assets/IAssetStatsEntity.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Assets/IAssetStatsEntity.cs rename to src/Squidex.Domain.Apps.Entities/Assets/IAssetStatsEntity.cs index 05f7b342d..7edf5ddb8 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetStatsEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/IAssetStatsEntity.cs @@ -8,7 +8,7 @@ using System; -namespace Squidex.Domain.Apps.Read.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public interface IAssetStatsEntity { diff --git a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs index 48c2ce66e..10a16b6e3 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs @@ -10,7 +10,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace Squidex.Domain.Apps.Read.Assets.Repositories +namespace Squidex.Domain.Apps.Entities.Assets.Repositories { public interface IAssetRepository { diff --git a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetStatsRepository.cs b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetStatsRepository.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetStatsRepository.cs rename to src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetStatsRepository.cs index 09159069b..5980dc6c1 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetStatsRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetStatsRepository.cs @@ -10,7 +10,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace Squidex.Domain.Apps.Read.Assets.Repositories +namespace Squidex.Domain.Apps.Entities.Assets.Repositories { public interface IAssetStatsRepository { diff --git a/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs new file mode 100644 index 000000000..b8ce1c92e --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs @@ -0,0 +1,91 @@ +// ========================================================================== +// AssetState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Assets; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Entities.Assets.State +{ + public class AssetState : DomainObjectState, + IAssetEntity, + IAssetInfo, + IUpdateableEntityWithAppRef + { + [JsonProperty] + public Guid AppId { get; set; } + + [JsonProperty] + public string FileName { get; set; } + + [JsonProperty] + public string MimeType { get; set; } + + [JsonProperty] + public long FileVersion { get; set; } + + [JsonProperty] + public long FileSize { get; set; } + + [JsonProperty] + public long TotalSize { get; set; } + + [JsonProperty] + public bool IsImage { get; set; } + + [JsonProperty] + public int? PixelWidth { get; set; } + + [JsonProperty] + public int? PixelHeight { get; set; } + + [JsonProperty] + public bool IsDeleted { get; set; } + + Guid IAssetInfo.AssetId + { + get { return Id; } + } + + protected void On(AssetCreated @event) + { + SimpleMapper.Map(@event, this); + + TotalSize += @event.FileSize; + } + + protected void On(AssetUpdated @event) + { + SimpleMapper.Map(@event, this); + + TotalSize += @event.FileSize; + } + + protected void On(AssetRenamed @event) + { + FileName = @event.FileName; + } + + protected void On(AssetDeleted @event) + { + IsDeleted = true; + } + + public AssetState Apply(Envelope @event) + { + var payload = (SquidexEvent)@event.Payload; + + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Read/CachingProviderBase.cs b/src/Squidex.Domain.Apps.Entities/CachingProviderBase.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read/CachingProviderBase.cs rename to src/Squidex.Domain.Apps.Entities/CachingProviderBase.cs index 5883b29ab..e625dc23d 100644 --- a/src/Squidex.Domain.Apps.Read/CachingProviderBase.cs +++ b/src/Squidex.Domain.Apps.Entities/CachingProviderBase.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Caching.Memory; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public abstract class CachingProviderBase { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs index 292f257ee..9628bbd3d 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Contents; -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public sealed class ChangeContentStatus : ContentCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs index edf62f437..e74e06d33 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs @@ -10,7 +10,7 @@ using System; using System.Security.Claims; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public abstract class ContentCommand : SchemaCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/ContentDataCommand.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/ContentDataCommand.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs index 02c147574..61cb005ed 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/ContentDataCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Contents; -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public abstract class ContentDataCommand : ContentCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/CreateContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/CreateContent.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs index 3065c0202..87b43582b 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/CreateContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public sealed class CreateContent : ContentDataCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/DeleteContent.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/DeleteContent.cs index 23638c95b..463f68010 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/DeleteContent.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public sealed class DeleteContent : ContentCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs index ff38a5638..5f250f517 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public sealed class PatchContent : ContentDataCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs index be9546173..3d6df181b 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Contents.Commands +namespace Squidex.Domain.Apps.Entities.Contents.Commands { public sealed class UpdateContent : ContentDataCommand { diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs similarity index 94% rename from src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 5c111c5bd..95d376ec6 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -9,16 +9,15 @@ using System; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Write.Contents.Commands; -using Squidex.Domain.Apps.Write.Contents.Guards; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Guards; +using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Dispatching; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public class ContentCommandMiddleware : ICommandMiddleware { @@ -84,7 +83,7 @@ namespace Squidex.Domain.Apps.Write.Contents content.Update(command); - context.Complete(new ContentDataChangedResult(content.Data, content.Version)); + context.Complete(new ContentDataChangedResult(content.State.Data, content.Version)); }); } @@ -101,7 +100,7 @@ namespace Squidex.Domain.Apps.Write.Contents content.Patch(command); - context.Complete(new ContentDataChangedResult(content.Data, content.Version)); + context.Complete(new ContentDataChangedResult(content.State.Data, content.Version)); }); } @@ -109,7 +108,7 @@ namespace Squidex.Domain.Apps.Write.Contents { return handler.UpdateAsync(context, async content => { - GuardContent.CanChangeContentStatus(content.Status, command); + GuardContent.CanChangeContentStatus(content.State.Status, command); var operationContext = await CreateContext(command, content, () => "Failed to patch content."); diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentDataChangedResult.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDataChangedResult.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/Contents/ContentDataChangedResult.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentDataChangedResult.cs index 9eb6b0432..1a8937e10 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentDataChangedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDataChangedResult.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentDataChangedResult : EntitySavedResult { diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs similarity index 62% rename from src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 482f31195..1c0a712d8 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -6,67 +6,19 @@ // All rights reserved. // ========================================================================== -using System; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { - public class ContentDomainObject : DomainObjectBase + public sealed class ContentDomainObject : DomainObjectBase { - private bool isDeleted; - private bool isCreated; - private Status status; - private NamedContentData data; - - public bool IsDeleted - { - get { return isDeleted; } - } - - public Status Status - { - get { return status; } - } - - public NamedContentData Data - { - get { return data; } - } - - public ContentDomainObject(Guid id, int version) - : base(id, version) - { - } - - protected void On(ContentCreated @event) - { - isCreated = true; - - data = @event.Data; - } - - protected void On(ContentUpdated @event) - { - data = @event.Data; - } - - protected void On(ContentStatusChanged @event) - { - status = @event.Status; - } - - protected void On(ContentDeleted @event) - { - isDeleted = true; - } - public ContentDomainObject Create(CreateContent command) { VerifyNotCreated(); @@ -103,7 +55,7 @@ namespace Squidex.Domain.Apps.Write.Contents { VerifyCreatedAndNotDeleted(); - if (!command.Data.Equals(Data)) + if (!command.Data.Equals(State.Data)) { RaiseEvent(SimpleMapper.Map(command, new ContentUpdated())); } @@ -115,9 +67,9 @@ namespace Squidex.Domain.Apps.Write.Contents { VerifyCreatedAndNotDeleted(); - var newData = command.Data.MergeInto(Data); + var newData = command.Data.MergeInto(State.Data); - if (!newData.Equals(Data)) + if (!newData.Equals(State.Data)) { var @event = SimpleMapper.Map(command, new ContentUpdated()); @@ -131,7 +83,7 @@ namespace Squidex.Domain.Apps.Write.Contents private void VerifyNotCreated() { - if (isCreated) + if (State.Data != null) { throw new DomainException("Content has already been created."); } @@ -139,15 +91,15 @@ namespace Squidex.Domain.Apps.Write.Contents private void VerifyCreatedAndNotDeleted() { - if (isDeleted || !isCreated) + if (State.IsDeleted || State.Data == null) { throw new DomainException("Content has already been deleted or not created yet."); } } - protected override void DispatchEvent(Envelope @event) + protected override void OnRaised(Envelope @event) { - this.DispatchAction(@event.Payload); + UpdateState(State.Apply(@event)); } } } diff --git a/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs index ab734aab4..fc0d29266 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs @@ -7,12 +7,12 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Read.History; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentHistoryEventsCreator : HistoryEventsCreatorBase { diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs similarity index 78% rename from src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 530c7505e..6024c0b47 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -13,18 +13,15 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Tasks; -#pragma warning disable IDE0017 // Simplify object initialization - -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentOperationContext { @@ -46,18 +43,19 @@ namespace Squidex.Domain.Apps.Write.Contents IScriptEngine scriptEngine, Func message) { - var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Name, command.SchemaId.Id); - - var context = new ContentOperationContext(); + var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Id, command.SchemaId.Id); - context.appEntity = appEntity; - context.assetRepository = assetRepository; - context.contentRepository = contentRepository; - context.content = content; - context.command = command; - context.message = message; - context.schemaEntity = schemaEntity; - context.scriptEngine = scriptEngine; + var context = new ContentOperationContext + { + appEntity = appEntity, + assetRepository = assetRepository, + contentRepository = contentRepository, + content = content, + command = command, + message = message, + schemaEntity = schemaEntity, + scriptEngine = scriptEngine + }; return context; } @@ -121,7 +119,7 @@ namespace Squidex.Domain.Apps.Write.Contents { if (command is ContentDataCommand dataCommand) { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString(), Data = dataCommand.Data }; + var ctx = new ScriptContext { ContentId = content.State.Id, OldData = content.State.Data, User = command.User, Operation = operation.ToString(), Data = dataCommand.Data }; dataCommand.Data = scriptEngine.ExecuteAndTransform(ctx, script(schemaEntity)); } @@ -131,7 +129,7 @@ namespace Squidex.Domain.Apps.Write.Contents public Task ExecuteScriptAsync(Func script, object operation) { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString() }; + var ctx = new ScriptContext { ContentId = content.State.Id, OldData = content.State.Data, User = command.User, Operation = operation.ToString() }; scriptEngine.Execute(ctx, script(schemaEntity)); diff --git a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs rename to src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index 46bb4848e..95f95ee31 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -16,15 +16,15 @@ using Microsoft.OData.UriParser; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Contents.Edm; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Contents.Edm; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public sealed class ContentQueryService : IContentQueryService { @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Read.Contents this.modelBuilder = modelBuilder; } - public async Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id) + public async Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = -1) { Guard.NotNull(app, nameof(app)); Guard.NotNull(user, nameof(user)); @@ -60,7 +60,10 @@ namespace Squidex.Domain.Apps.Read.Contents var schema = await FindSchemaAsync(app, schemaIdOrName); - var content = await contentRepository.FindContentAsync(app, schema, id); + var content = + version > EtagVersion.Empty ? + await contentRepository.FindContentAsync(app, schema, id, version) : + await contentRepository.FindContentAsync(app, schema, id); if (content == null || (content.Status != Status.Published && !isFrontendClient)) { @@ -155,12 +158,12 @@ namespace Squidex.Domain.Apps.Read.Contents if (Guid.TryParse(schemaIdOrName, out var id)) { - schema = await appProvider.GetSchemaAsync(app.Name, id); + schema = await appProvider.GetSchemaAsync(app.Id, id); } if (schema == null) { - schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName); + schema = await appProvider.GetSchemaAsync(app.Id, schemaIdOrName); } if (schema == null) diff --git a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs b/src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs index 696d5282d..bf67aa1f6 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs @@ -12,11 +12,11 @@ using Microsoft.OData.Edm; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.Edm +namespace Squidex.Domain.Apps.Entities.Contents.Edm { public class EdmModelBuilder : CachingProviderBase { diff --git a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs b/src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelExtensions.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelExtensions.cs index 7da78c5be..4e414d746 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelExtensions.cs @@ -11,7 +11,7 @@ using System.Linq; using Microsoft.OData.Edm; using Microsoft.OData.UriParser; -namespace Squidex.Domain.Apps.Read.Contents.Edm +namespace Squidex.Domain.Apps.Entities.Contents.Edm { public static class EdmModelExtensions { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index 17d31bb7d..b1647d210 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -11,15 +11,15 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService { - private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60); + private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private readonly IContentQueryService contentQuery; private readonly IGraphQLUrlGenerator urlGenerator; private readonly IAssetRepository assetRepository; @@ -61,13 +61,13 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL private async Task GetModelAsync(IAppEntity app) { - var cacheKey = CreateCacheKey(app.Id, app.Etag); + var cacheKey = CreateCacheKey(app.Id, app.Version.ToString()); var modelContext = Cache.Get(cacheKey); if (modelContext == null) { - var allSchemas = await appProvider.GetSchemasAsync(app.Name); + var allSchemas = await appProvider.GetSchemasAsync(app.Id); modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator); diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs index 292cc138b..05921a5ca 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs @@ -16,14 +16,14 @@ using GraphQL.Types; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Contents.GraphQL.Types; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using GraphQLSchema = GraphQL.Types.Schema; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class GraphQLModel : IGraphQLContext { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQuery.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQuery.cs index 476230a3a..6d4163321 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQuery.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Linq; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public class GraphQLQuery { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQueryContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs similarity index 90% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQueryContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs index e1abc0316..8ed50cea9 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQueryContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLQueryContext.cs @@ -11,11 +11,11 @@ using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class GraphQLQueryContext : QueryContext { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs index 30e8afdcf..1bd15fb37 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLContext.cs @@ -11,9 +11,9 @@ using GraphQL.Resolvers; using GraphQL.Types; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public interface IGraphQLContext { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs similarity index 85% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLService.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs index 1181813c3..8b0ce31b2 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLService.cs @@ -8,9 +8,9 @@ using System.Security.Claims; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Entities.Apps; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public interface IGraphQLService { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLUrlGenerator.cs similarity index 80% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLUrlGenerator.cs index 849004c0f..983778efc 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/IGraphQLUrlGenerator.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLUrlGenerator.cs @@ -6,11 +6,11 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Schemas; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public interface IGraphQLUrlGenerator { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs similarity index 98% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 4d2b04e26..c75d1c80b 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -9,10 +9,10 @@ using System; using GraphQL.Resolvers; using GraphQL.Types; -using Squidex.Domain.Apps.Read.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class AssetGraphType : ObjectGraphType { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs similarity index 97% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentDataGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index b342efa6b..af48d1087 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Schema = Squidex.Domain.Apps.Core.Schemas.Schema; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentDataGraphType : ObjectGraphType { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs similarity index 97% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index 93019e19d..0ef578e8a 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -10,10 +10,10 @@ using System; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentGraphType : ObjectGraphType { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentQueryGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentQueryGraphType.cs similarity index 98% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentQueryGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentQueryGraphType.cs index b0e3fb5ba..4d980761d 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/ContentQueryGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentQueryGraphType.cs @@ -11,10 +11,10 @@ using System.Collections.Generic; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class ContentQueryGraphType : ObjectGraphType { diff --git a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/NoopGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/NoopGraphType.cs rename to src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs index 36218f6eb..90c87258c 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/NoopGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NoopGraphType.cs @@ -10,7 +10,7 @@ using System; using GraphQL.Language.AST; using GraphQL.Types; -namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { public sealed class NoopGraphType : ScalarGraphType { diff --git a/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs similarity index 95% rename from src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index 06178cdb1..f93c685bc 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -7,10 +7,10 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Contents.Guards +namespace Squidex.Domain.Apps.Entities.Contents.Guards { public static class GuardContent { diff --git a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs rename to src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs index 380335382..7e3ae5451 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentEntity.cs @@ -9,7 +9,7 @@ using Squidex.Domain.Apps.Core.Contents; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentEntity : IEntity, diff --git a/src/Squidex.Domain.Apps.Read/Contents/IContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs similarity index 83% rename from src/Squidex.Domain.Apps.Read/Contents/IContentQueryService.cs rename to src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs index 0af89adcf..8baaccd33 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/IContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentQueryService.cs @@ -10,10 +10,11 @@ using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentQueryService { @@ -21,7 +22,7 @@ namespace Squidex.Domain.Apps.Read.Contents Task<(ISchemaEntity Schema, long Total, IReadOnlyList Items)> QueryWithCountAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, bool archived, string query); - Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id); + Task<(ISchemaEntity Schema, IContentEntity Content)> FindContentAsync(IAppEntity app, string schemaIdOrName, ClaimsPrincipal user, Guid id, long version = EtagVersion.Any); Task FindSchemaAsync(IAppEntity app, string schemaIdOrName); } diff --git a/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs rename to src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs index 195808e11..01288799e 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/QueryContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/QueryContext.cs @@ -12,12 +12,12 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public class QueryContext { @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Read.Contents } } - return ids.Select(id => cachedAssets.GetOrDefault(id)).Where(x => x != null).ToList(); + return ids.Select(cachedAssets.GetOrDefault).Where(x => x != null).ToList(); } public async Task> GetReferencedContentsAsync(Guid schemaId, ICollection ids) @@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Read.Contents } } - return ids.Select(id => cachedContents.GetOrDefault(id)).Where(x => x != null).ToList(); + return ids.Select(cachedContents.GetOrDefault).Where(x => x != null).ToList(); } } } diff --git a/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs similarity index 83% rename from src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs rename to src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index 85887ce9a..857cc189a 100644 --- a/src/Squidex.Domain.Apps.Read/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -11,10 +11,10 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.OData.UriParser; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; -namespace Squidex.Domain.Apps.Read.Contents.Repositories +namespace Squidex.Domain.Apps.Entities.Contents.Repositories { public interface IContentRepository { @@ -29,5 +29,7 @@ namespace Squidex.Domain.Apps.Read.Contents.Repositories Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery); Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id); + + Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, long version); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs new file mode 100644 index 000000000..f3d09dca4 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// ContentState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities.Contents.State +{ + public class ContentState : DomainObjectState, + IContentEntity, + IUpdateableEntityWithAppRef + { + [JsonProperty] + public NamedContentData Data { get; set; } + + [JsonProperty] + public Guid AppId { get; set; } + + [JsonProperty] + public Guid SchemaId { get; set; } + + [JsonProperty] + public Status Status { get; set; } + + [JsonProperty] + public bool IsDeleted { get; set; } + + protected void On(ContentCreated @event) + { + SchemaId = @event.SchemaId.Id; + + Data = @event.Data; + } + + protected void On(ContentUpdated @event) + { + Data = @event.Data; + } + + protected void On(ContentStatusChanged @event) + { + Status = @event.Status; + } + + protected void On(ContentDeleted @event) + { + IsDeleted = true; + } + + public ContentState Apply(Envelope @event) + { + var payload = (SquidexEvent)@event.Payload; + + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/JsonEntity.cs b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs similarity index 56% rename from src/Squidex.Domain.Apps.Read/State/Grains/JsonEntity.cs rename to src/Squidex.Domain.Apps.Entities/DomainObjectState.cs index 94d38176e..dc311eb6c 100644 --- a/src/Squidex.Domain.Apps.Read/State/Grains/JsonEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs @@ -1,5 +1,5 @@ // ========================================================================== -// JsonEntity.cs +// DomainObjectState.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -10,14 +10,30 @@ using System; using Newtonsoft.Json; using NodaTime; using Squidex.Infrastructure; +using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Read.State.Grains +namespace Squidex.Domain.Apps.Entities { - public abstract class JsonEntity : Cloneable, IUpdateableEntityWithVersion where T : Cloneable + public abstract class DomainObjectState : Cloneable, + IDomainState, + IEntity, + IEntityWithCreatedBy, + IEntityWithLastModifiedBy, + IEntityWithVersion, + IUpdateableEntity, + IUpdateableEntityWithCreatedBy, + IUpdateableEntityWithLastModifiedBy + where T : Cloneable { [JsonProperty] public Guid Id { get; set; } + [JsonProperty] + public RefToken CreatedBy { get; set; } + + [JsonProperty] + public RefToken LastModifiedBy { get; set; } + [JsonProperty] public Instant Created { get; set; } diff --git a/src/Squidex.Domain.Apps.Entities/EntityMapper.cs b/src/Squidex.Domain.Apps.Entities/EntityMapper.cs new file mode 100644 index 000000000..81bb15aa1 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/EntityMapper.cs @@ -0,0 +1,89 @@ +// ========================================================================== +// EntityMapper2.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using NodaTime; +using Squidex.Domain.Apps.Events; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities +{ + public static class EntityMapper + { + public static T Update(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity + { + SetId(entity, headers); + SetAppId(entity, @event); + SetCreated(entity, headers); + SetCreatedBy(entity, @event); + SetLastModified(entity, headers); + SetLastModifiedBy(entity, @event); + SetVersion(entity, headers); + + updater?.Invoke(entity); + + return entity; + } + + private static void SetId(IEntity entity, EnvelopeHeaders headers) + { + if (entity is IUpdateableEntity updateable && updateable.Id == Guid.Empty) + { + updateable.Id = headers.AggregateId(); + } + } + + private static void SetVersion(IEntity entity, EnvelopeHeaders headers) + { + if (entity is IUpdateableEntityWithVersion updateable) + { + updateable.Version = headers.EventStreamNumber(); + } + } + + private static void SetCreated(IEntity entity, EnvelopeHeaders headers) + { + if (entity is IUpdateableEntity updateable && updateable.Created == default(Instant)) + { + updateable.Created = headers.Timestamp(); + } + } + + private static void SetCreatedBy(IEntity entity, SquidexEvent @event) + { + if (entity is IUpdateableEntityWithCreatedBy withCreatedBy && withCreatedBy.CreatedBy == null) + { + withCreatedBy.CreatedBy = @event.Actor; + } + } + + private static void SetLastModified(IEntity entity, EnvelopeHeaders headers) + { + if (entity is IUpdateableEntity updateable) + { + updateable.LastModified = headers.Timestamp(); + } + } + + private static void SetLastModifiedBy(IEntity entity, SquidexEvent @event) + { + if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) + { + withModifiedBy.LastModifiedBy = @event.Actor; + } + } + + private static void SetAppId(IEntity entity, SquidexEvent @event) + { + if (entity is IUpdateableEntityWithAppRef appEntity && @event is AppEvent appEvent) + { + appEntity.AppId = appEvent.AppId.Id; + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Read/History/HistoryEventToStore.cs b/src/Squidex.Domain.Apps.Entities/History/HistoryEventToStore.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read/History/HistoryEventToStore.cs rename to src/Squidex.Domain.Apps.Entities/History/HistoryEventToStore.cs index 1ca32b31a..aeda6bc40 100644 --- a/src/Squidex.Domain.Apps.Read/History/HistoryEventToStore.cs +++ b/src/Squidex.Domain.Apps.Entities/History/HistoryEventToStore.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.History +namespace Squidex.Domain.Apps.Entities.History { public sealed class HistoryEventToStore { diff --git a/src/Squidex.Domain.Apps.Read/History/HistoryEventsCreatorBase.cs b/src/Squidex.Domain.Apps.Entities/History/HistoryEventsCreatorBase.cs similarity index 97% rename from src/Squidex.Domain.Apps.Read/History/HistoryEventsCreatorBase.cs rename to src/Squidex.Domain.Apps.Entities/History/HistoryEventsCreatorBase.cs index a267d1572..e3a59eecd 100644 --- a/src/Squidex.Domain.Apps.Read/History/HistoryEventsCreatorBase.cs +++ b/src/Squidex.Domain.Apps.Entities/History/HistoryEventsCreatorBase.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.History +namespace Squidex.Domain.Apps.Entities.History { public abstract class HistoryEventsCreatorBase : IHistoryEventsCreator { diff --git a/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs b/src/Squidex.Domain.Apps.Entities/History/IHistoryEventEntity.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs rename to src/Squidex.Domain.Apps.Entities/History/IHistoryEventEntity.cs index a9b213b5c..ae201227e 100644 --- a/src/Squidex.Domain.Apps.Read/History/IHistoryEventEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/History/IHistoryEventEntity.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.History +namespace Squidex.Domain.Apps.Entities.History { public interface IHistoryEventEntity : IEntity { diff --git a/src/Squidex.Domain.Apps.Read/History/IHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Entities/History/IHistoryEventsCreator.cs similarity index 93% rename from src/Squidex.Domain.Apps.Read/History/IHistoryEventsCreator.cs rename to src/Squidex.Domain.Apps.Entities/History/IHistoryEventsCreator.cs index 91d37234e..38fadac6b 100644 --- a/src/Squidex.Domain.Apps.Read/History/IHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Entities/History/IHistoryEventsCreator.cs @@ -10,7 +10,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.History +namespace Squidex.Domain.Apps.Entities.History { public interface IHistoryEventsCreator { diff --git a/src/Squidex.Domain.Apps.Read/History/Repositories/IHistoryEventRepository.cs b/src/Squidex.Domain.Apps.Entities/History/Repositories/IHistoryEventRepository.cs similarity index 90% rename from src/Squidex.Domain.Apps.Read/History/Repositories/IHistoryEventRepository.cs rename to src/Squidex.Domain.Apps.Entities/History/Repositories/IHistoryEventRepository.cs index b0875f5d2..7652fbd28 100644 --- a/src/Squidex.Domain.Apps.Read/History/Repositories/IHistoryEventRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/History/Repositories/IHistoryEventRepository.cs @@ -10,7 +10,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace Squidex.Domain.Apps.Read.History.Repositories +namespace Squidex.Domain.Apps.Entities.History.Repositories { public interface IHistoryEventRepository { diff --git a/src/Squidex.Domain.Apps.Read/IAppProvider.cs b/src/Squidex.Domain.Apps.Entities/IAppProvider.cs similarity index 54% rename from src/Squidex.Domain.Apps.Read/IAppProvider.cs rename to src/Squidex.Domain.Apps.Entities/IAppProvider.cs index 296f8ff2d..a8bef2d45 100644 --- a/src/Squidex.Domain.Apps.Read/IAppProvider.cs +++ b/src/Squidex.Domain.Apps.Entities/IAppProvider.cs @@ -1,5 +1,5 @@ // ========================================================================== -// IApps.cs +// IAppProvider.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,25 +9,25 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Schemas; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IAppProvider { - Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id); + Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid appId, Guid id); Task GetAppAsync(string appName); - Task GetSchemaAsync(string appName, Guid id, bool provideDeleted = false); + Task GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false); - Task GetSchemaAsync(string appName, string name, bool provideDeleted = false); + Task GetSchemaAsync(Guid appId, string name, bool provideDeleted = false); - Task> GetSchemasAsync(string appName); + Task> GetSchemasAsync(Guid appId); - Task> GetRulesAsync(string appName); + Task> GetRulesAsync(Guid appId); Task> GetUserApps(string userId); } diff --git a/src/Squidex.Domain.Apps.Entities/IEntity.cs b/src/Squidex.Domain.Apps.Entities/IEntity.cs new file mode 100644 index 000000000..a23a78d08 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/IEntity.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// IEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using NodaTime; + +namespace Squidex.Domain.Apps.Entities +{ + public interface IEntity + { + Guid Id { get; } + + Instant Created { get; } + + Instant LastModified { get; } + } +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs rename to src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs index fdd81b0e1..2b7549767 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs +++ b/src/Squidex.Domain.Apps.Entities/IEntityWithAppRef.cs @@ -8,7 +8,7 @@ using System; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IEntityWithAppRef { diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs b/src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs rename to src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs index 4661a15ee..a8ff4bb95 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs +++ b/src/Squidex.Domain.Apps.Entities/IEntityWithCreatedBy.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IEntityWithCreatedBy { diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithLastModifiedBy.cs b/src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IEntityWithLastModifiedBy.cs rename to src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs index eb42b4e93..d5704ac7a 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithLastModifiedBy.cs +++ b/src/Squidex.Domain.Apps.Entities/IEntityWithLastModifiedBy.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IEntityWithLastModifiedBy { diff --git a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs b/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs similarity index 91% rename from src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs rename to src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs index 860918deb..9ec4e7fa9 100644 --- a/src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs +++ b/src/Squidex.Domain.Apps.Entities/IEntityWithVersion.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IEntityWithVersion { diff --git a/src/Squidex.Domain.Apps.Read/IEntity.cs b/src/Squidex.Domain.Apps.Entities/IUpdateableEntity.cs similarity index 81% rename from src/Squidex.Domain.Apps.Read/IEntity.cs rename to src/Squidex.Domain.Apps.Entities/IUpdateableEntity.cs index 89aea8bed..7e1b2f03d 100644 --- a/src/Squidex.Domain.Apps.Read/IEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/IUpdateableEntity.cs @@ -1,5 +1,5 @@ // ========================================================================== -// IEntity.cs +// IUpdateableEntity.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -9,9 +9,9 @@ using System; using NodaTime; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { - public interface IEntity + public interface IUpdateableEntity { Guid Id { get; set; } @@ -19,4 +19,4 @@ namespace Squidex.Domain.Apps.Read Instant LastModified { get; set; } } -} \ No newline at end of file +} diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs rename to src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs index 316962a96..aa3b58226 100644 --- a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs +++ b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithAppRef.cs @@ -8,7 +8,7 @@ using System; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IUpdateableEntityWithAppRef { diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithCreatedBy.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs rename to src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithCreatedBy.cs index 3675937c4..8f07b421d 100644 --- a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs +++ b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithCreatedBy.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IUpdateableEntityWithCreatedBy { diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithLastModifiedBy.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs rename to src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithLastModifiedBy.cs index d1aedc9f4..c57cf3d75 100644 --- a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs +++ b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithLastModifiedBy.cs @@ -8,7 +8,7 @@ using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IUpdateableEntityWithLastModifiedBy { diff --git a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithVersion.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs rename to src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithVersion.cs index 8d08b4c6a..229f7ea2d 100644 --- a/src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs +++ b/src/Squidex.Domain.Apps.Entities/IUpdateableEntityWithVersion.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read +namespace Squidex.Domain.Apps.Entities { public interface IUpdateableEntityWithVersion { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs index 1c6bafd7e..1018ea503 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/CreateRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/CreateRule.cs @@ -8,7 +8,7 @@ using System; -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public sealed class CreateRule : RuleEditCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs index c97f2c6b1..96edbb03b 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/DeleteRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DeleteRule.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public sealed class DeleteRule : RuleAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs index ccfa2a9be..c4419b680 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/DisableRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/DisableRule.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public sealed class DisableRule : RuleAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs index ac3bf7f4c..8a4f9a746 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/EnableRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/EnableRule.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public sealed class EnableRule : RuleAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs index d240c8957..2b28578ad 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleAggregateCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public abstract class RuleAggregateCommand : AppCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs similarity index 91% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs index b63be728a..548e34a9e 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/RuleEditCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/RuleEditCommand.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Rules; -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public abstract class RuleEditCommand : RuleAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Commands/UpdateRule.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Commands/UpdateRule.cs index 6fe43df4d..841248dcc 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Commands/UpdateRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Commands/UpdateRule.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Rules.Commands +namespace Squidex.Domain.Apps.Entities.Rules.Commands { public sealed class UpdateRule : RuleEditCommand { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index 5848f9f1e..ed9549606 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Guards/GuardRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -8,11 +8,10 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Rules.Guards +namespace Squidex.Domain.Apps.Entities.Rules.Guards { public static class GuardRule { @@ -28,7 +27,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards } else { - var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider); errors.Foreach(error); } @@ -59,7 +58,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards if (command.Trigger != null) { - var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Name, command.Trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(command.AppId.Id, command.Trigger, appProvider); errors.Foreach(error); } diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs similarity index 96% rename from src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs index 4ead12956..c1a9578d3 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleActionValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleActionValidator.cs @@ -12,7 +12,7 @@ using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Rules.Guards +namespace Squidex.Domain.Apps.Entities.Rules.Guards { public sealed class RuleActionValidator : IRuleActionVisitor>> { diff --git a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs index 48dfec259..faed510c2 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/Guards/RuleTriggerValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs @@ -12,11 +12,10 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Rules.Guards +namespace Squidex.Domain.Apps.Entities.Rules.Guards { public sealed class RuleTriggerValidator : IRuleTriggerVisitor>> { @@ -27,12 +26,12 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards SchemaProvider = schemaProvider; } - public static Task> ValidateAsync(string appName, RuleTrigger action, IAppProvider appProvider) + public static Task> ValidateAsync(Guid appId, RuleTrigger action, IAppProvider appProvider) { Guard.NotNull(action, nameof(action)); Guard.NotNull(appProvider, nameof(appProvider)); - var visitor = new RuleTriggerValidator(x => appProvider.GetSchemaAsync(appName, x)); + var visitor = new RuleTriggerValidator(x => appProvider.GetSchemaAsync(appId, x)); return action.Accept(visitor); } diff --git a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs similarity index 92% rename from src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs rename to src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs index b9f1aeaa2..a358e1c0e 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEntity.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Rules; -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public interface IRuleEntity : IEntity, diff --git a/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs rename to src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs index 631ad2322..7b499253f 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/IRuleEventEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/IRuleEventEntity.cs @@ -10,7 +10,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public interface IRuleEventEntity : IEntity { diff --git a/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs b/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs similarity index 95% rename from src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs index 2b05e6741..7bf7acb45 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs @@ -14,7 +14,7 @@ using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -namespace Squidex.Domain.Apps.Read.Rules.Repositories +namespace Squidex.Domain.Apps.Entities.Rules.Repositories { public interface IRuleEventRepository { diff --git a/src/Squidex.Domain.Apps.Write/Contents/IContentVersionLoader.cs b/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs similarity index 61% rename from src/Squidex.Domain.Apps.Write/Contents/IContentVersionLoader.cs rename to src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs index e3968fbfc..2dbbfe1e6 100644 --- a/src/Squidex.Domain.Apps.Write/Contents/IContentVersionLoader.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleRepository.cs @@ -1,5 +1,5 @@ // ========================================================================== -// IContentVersionLoader.cs +// IRuleRepository.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -7,13 +7,13 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Contents; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Rules.Repositories { - public interface IContentVersionLoader + public interface IRuleRepository { - Task LoadAsync(Guid appId, Guid id, long version); + Task> QueryRuleIdsAsync(Guid appId); } } diff --git a/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs similarity index 76% rename from src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs rename to src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs index 302951f90..1217d9105 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/RuleCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleCommandMiddleware.cs @@ -8,14 +8,13 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Write.Rules.Commands; -using Squidex.Domain.Apps.Write.Rules.Guards; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.Guards; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Dispatching; -namespace Squidex.Domain.Apps.Write.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public class RuleCommandMiddleware : ICommandMiddleware { @@ -34,7 +33,7 @@ namespace Squidex.Domain.Apps.Write.Rules protected Task On(CreateRule command, CommandContext context) { - return handler.CreateAsync(context, async w => + return handler.CreateSyncedAsync(context, async w => { await GuardRule.CanCreate(command, appProvider); @@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Write.Rules protected Task On(UpdateRule command, CommandContext context) { - return handler.UpdateAsync(context, async c => + return handler.UpdateSyncedAsync(context, async c => { await GuardRule.CanUpdate(command, appProvider); @@ -54,9 +53,9 @@ namespace Squidex.Domain.Apps.Write.Rules protected Task On(EnableRule command, CommandContext context) { - return handler.UpdateAsync(context, r => + return handler.UpdateSyncedAsync(context, r => { - GuardRule.CanEnable(command, r.Rule); + GuardRule.CanEnable(command, r.State.RuleDef); r.Enable(command); }); @@ -64,9 +63,9 @@ namespace Squidex.Domain.Apps.Write.Rules protected Task On(DisableRule command, CommandContext context) { - return handler.UpdateAsync(context, r => + return handler.UpdateSyncedAsync(context, r => { - GuardRule.CanDisable(command, r.Rule); + GuardRule.CanDisable(command, r.State.RuleDef); r.Disable(command); }); @@ -74,7 +73,7 @@ namespace Squidex.Domain.Apps.Write.Rules protected Task On(DeleteRule command, CommandContext context) { - return handler.UpdateAsync(context, c => + return handler.UpdateSyncedAsync(context, c => { GuardRule.CanDelete(command); diff --git a/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs rename to src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs index b738dfeef..5ae2477bf 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleDequeuer.cs @@ -14,14 +14,14 @@ using System.Threading.Tasks.Dataflow; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Timers; -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { - public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem + public class RuleDequeuer : DisposableObjectBase, IExternalSystem { private readonly ActionBlock requestBlock; private readonly IRuleEventRepository ruleEventRepository; diff --git a/src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs similarity index 59% rename from src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs index 82a679cc8..8b7a94918 100644 --- a/src/Squidex.Domain.Apps.Write/Rules/RuleDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleDomainObject.cs @@ -6,59 +6,18 @@ // All rights reserved. // ========================================================================== -using System; -using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Events.Rules; -using Squidex.Domain.Apps.Events.Rules.Utils; -using Squidex.Domain.Apps.Write.Rules.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Write.Rules +namespace Squidex.Domain.Apps.Entities.Rules { - public class RuleDomainObject : DomainObjectBase + public sealed class RuleDomainObject : DomainObjectBase { - private Rule rule; - private bool isDeleted; - - public Rule Rule - { - get { return rule; } - } - - public RuleDomainObject(Guid id, int version) - : base(id, version) - { - } - - protected void On(RuleCreated @event) - { - rule = RuleEventDispatcher.Create(@event); - } - - protected void On(RuleUpdated @event) - { - rule = rule.Apply(@event); - } - - protected void On(RuleEnabled @event) - { - rule = rule.Apply(@event); - } - - protected void On(RuleDisabled @event) - { - rule = rule.Apply(@event); - } - - protected void On(RuleDeleted @event) - { - isDeleted = true; - } - public void Create(CreateRule command) { VerifyNotCreated(); @@ -96,7 +55,7 @@ namespace Squidex.Domain.Apps.Write.Rules private void VerifyNotCreated() { - if (rule != null) + if (State.RuleDef != null) { throw new DomainException("Webhook has already been created."); } @@ -104,15 +63,15 @@ namespace Squidex.Domain.Apps.Write.Rules private void VerifyCreatedAndNotDeleted() { - if (isDeleted || rule == null) + if (State.IsDeleted || State.RuleDef == null) { throw new DomainException("Webhook has already been deleted or not created yet."); } } - protected override void DispatchEvent(Envelope @event) + protected override void OnRaised(Envelope @event) { - this.DispatchAction(@event.Payload); + UpdateState(State.Apply(@event)); } } } diff --git a/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs rename to src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs index 867b13937..d3fcbf959 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs @@ -8,13 +8,13 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Read.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Tasks; -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public sealed class RuleEnqueuer : IEventConsumer { @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Read.Rules { if (@event.Payload is AppEvent appEvent) { - var rules = await appProvider.GetRulesAsync(appEvent.AppId.Name); + var rules = await appProvider.GetRulesAsync(appEvent.AppId.Id); foreach (var ruleEntity in rules) { diff --git a/src/Squidex.Domain.Apps.Read/Rules/RuleJobResult.cs b/src/Squidex.Domain.Apps.Entities/Rules/RuleJobResult.cs similarity index 90% rename from src/Squidex.Domain.Apps.Read/Rules/RuleJobResult.cs rename to src/Squidex.Domain.Apps.Entities/Rules/RuleJobResult.cs index d8fb997e7..79dde166a 100644 --- a/src/Squidex.Domain.Apps.Read/Rules/RuleJobResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/RuleJobResult.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public enum RuleJobResult { diff --git a/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs new file mode 100644 index 000000000..2e593c7b0 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs @@ -0,0 +1,65 @@ +// ========================================================================== +// RuleState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Rules; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; + +namespace Squidex.Domain.Apps.Entities.Rules.State +{ + public class RuleState : DomainObjectState, + IRuleEntity, + IEntityWithAppRef, + IUpdateableEntityWithAppRef + { + [JsonProperty] + public Guid AppId { get; set; } + + [JsonProperty] + public Rule RuleDef { get; set; } + + [JsonProperty] + public bool IsDeleted { get; set; } + + protected void On(RuleCreated @event) + { + RuleDef = new Rule(@event.Trigger, @event.Action); + } + + protected void On(RuleUpdated @event) + { + RuleDef = RuleDef.Update(@event.Trigger).Update(@event.Action); + } + + protected void On(RuleEnabled @event) + { + RuleDef = RuleDef.Enable(); + } + + protected void On(RuleDisabled @event) + { + RuleDef = RuleDef.Disable(); + } + + protected void On(RuleDeleted @event) + { + IsDeleted = true; + } + + public RuleState Apply(Envelope @event) + { + var payload = (SquidexEvent)@event.Payload; + + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/SchemaAggregateCommand.cs b/src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs similarity index 93% rename from src/Squidex.Domain.Apps.Write/SchemaAggregateCommand.cs rename to src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs index fc5e43ae8..72bc2990a 100644 --- a/src/Squidex.Domain.Apps.Write/SchemaAggregateCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/SchemaAggregateCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write +namespace Squidex.Domain.Apps.Entities { public abstract class SchemaAggregateCommand : SchemaCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/SchemaCommand.cs b/src/Squidex.Domain.Apps.Entities/SchemaCommand.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/SchemaCommand.cs rename to src/Squidex.Domain.Apps.Entities/SchemaCommand.cs index 1d36b3a18..d962f9931 100644 --- a/src/Squidex.Domain.Apps.Write/SchemaCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/SchemaCommand.cs @@ -9,7 +9,7 @@ using System; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write +namespace Squidex.Domain.Apps.Entities { public abstract class SchemaCommand : AppCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/AddField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs similarity index 91% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/AddField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs index 8b78836df..40fea2376 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/AddField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/AddField.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class AddField : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ConfigureScripts.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/ConfigureScripts.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs index 08453ecb7..685006d55 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ConfigureScripts.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ConfigureScripts.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class ConfigureScripts : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs index b8a9bbcd0..ec389bc7b 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchema.cs @@ -9,9 +9,9 @@ using System; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Commands; -using SchemaFields = System.Collections.Generic.List; +using SchemaFields = System.Collections.Generic.List; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class CreateSchema : AppCommand, IAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs similarity index 92% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs index d532a0a76..49c84f3ea 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/CreateSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/CreateSchemaField.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class CreateSchemaField { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteField.cs index 36ea19310..cc8e5f572 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class DeleteField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs index 1384453b0..c5d61e764 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DeleteSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DeleteSchema.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class DeleteSchema : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DisableField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/DisableField.cs index 95dc2d327..7ab6a58df 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/DisableField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/DisableField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class DisableField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/EnableField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/EnableField.cs index 1ac8930b6..7905de7d8 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/EnableField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/EnableField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class EnableField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs index 4fe5f6b6f..54396bec6 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/FieldCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/FieldCommand.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public class FieldCommand : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/HideField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/HideField.cs index b8fcde46f..96a65bc88 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/HideField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/HideField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class HideField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/LockField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/LockField.cs index 3109fcb6e..705221a9a 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/LockField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/LockField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class LockField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs index a0385926d..4bf22c436 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/PublishSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/PublishSchema.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class PublishSchema : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ReorderFields.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/ReorderFields.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs index 2d3805fb1..af23659f6 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ReorderFields.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ReorderFields.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class ReorderFields : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ShowField.cs similarity index 87% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/ShowField.cs index 024148139..7e93cb85f 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/ShowField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/ShowField.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class ShowField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs similarity index 88% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs index f8e83ce02..ace984eb1 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UnpublishSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UnpublishSchema.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class UnpublishSchema : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateField.cs similarity index 89% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateField.cs index 1f70668cb..6756cd4ba 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateField.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class UpdateField : FieldCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs similarity index 90% rename from src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs index ba711a5a0..0f0f49252 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Commands/UpdateSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Commands/UpdateSchema.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Write.Schemas.Commands +namespace Squidex.Domain.Apps.Entities.Schemas.Commands { public sealed class UpdateSchema : SchemaAggregateCommand { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs similarity index 99% rename from src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index f08405643..0102e441f 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -1,5 +1,5 @@ // ========================================================================== -// FieldPropertiesValidator.cs +// FieldpropertiesValidator.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group @@ -11,7 +11,7 @@ using System.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Schemas.Guards +namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public sealed class FieldPropertiesValidator : IFieldPropertiesVisitor> { diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs similarity index 96% rename from src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index b9006a315..420520df2 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -10,11 +10,10 @@ using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Schemas.Guards +namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public static class GuardSchema { @@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards error(new ValidationError("Name must be a valid slug.", nameof(command.Name))); } - if (await appProvider.GetSchemaAsync(command.AppId.Name, command.Name) != null) + if (await appProvider.GetSchemaAsync(command.AppId.Id, command.Name) != null) { error(new ValidationError($"A schema with name '{command.Name}' already exists", nameof(command.Name))); } diff --git a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs similarity index 97% rename from src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchemaField.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 67a9eeb77..dfa761112 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -8,10 +8,10 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Write.Schemas.Guards +namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public static class GuardSchemaField { diff --git a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs similarity index 94% rename from src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs index 3cb98ef78..34bcc8d9a 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/ISchemaEntity.cs @@ -8,7 +8,7 @@ using Squidex.Domain.Apps.Core.Schemas; -namespace Squidex.Domain.Apps.Read.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { public interface ISchemaEntity : IEntity, diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs new file mode 100644 index 000000000..642072660 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Repositories/ISchemaRepository.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// ISchemaRepository.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Squidex.Domain.Apps.Entities.Schemas.Repositories +{ + public interface ISchemaRepository + { + Task FindSchemaIdAsync(Guid appId, string name); + + Task> QuerySchemaIdsAsync(Guid appId); + } +} diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs similarity index 59% rename from src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs index 71837b91a..bcb2c632d 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaCommandMiddleware.cs @@ -9,14 +9,13 @@ using System; using System.Linq; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Write.Schemas.Commands; -using Squidex.Domain.Apps.Write.Schemas.Guards; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Guards; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Dispatching; -namespace Squidex.Domain.Apps.Write.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { public class SchemaCommandMiddleware : ICommandMiddleware { @@ -35,33 +34,33 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(CreateSchema command, CommandContext context) { - return handler.CreateAsync(context, async s => + return handler.CreateSyncedAsync(context, async s => { await GuardSchema.CanCreate(command, appProvider); s.Create(command); - context.Complete(EntityCreatedResult.Create(s.Id, s.Version)); + context.Complete(EntityCreatedResult.Create(command.SchemaId, s.Version)); }); } protected Task On(AddField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanAdd(s.Schema, command); + GuardSchemaField.CanAdd(s.State.SchemaDef, command); s.Add(command); - context.Complete(EntityCreatedResult.Create(s.Schema.FieldsById.Values.First(x => x.Name == command.Name).Id, s.Version)); + context.Complete(EntityCreatedResult.Create(s.State.SchemaDef.FieldsById.Values.First(x => x.Name == command.Name).Id, s.Version)); }); } protected Task On(DeleteField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanDelete(s.Schema, command); + GuardSchemaField.CanDelete(s.State.SchemaDef, command); s.DeleteField(command); }); @@ -69,9 +68,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(LockField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanLock(s.Schema, command); + GuardSchemaField.CanLock(s.State.SchemaDef, command); s.LockField(command); }); @@ -79,9 +78,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(HideField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanHide(s.Schema, command); + GuardSchemaField.CanHide(s.State.SchemaDef, command); s.HideField(command); }); @@ -89,9 +88,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(ShowField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanShow(s.Schema, command); + GuardSchemaField.CanShow(s.State.SchemaDef, command); s.ShowField(command); }); @@ -99,9 +98,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(DisableField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanDisable(s.Schema, command); + GuardSchemaField.CanDisable(s.State.SchemaDef, command); s.DisableField(command); }); @@ -109,9 +108,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(EnableField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanEnable(s.Schema, command); + GuardSchemaField.CanEnable(s.State.SchemaDef, command); s.EnableField(command); }); @@ -119,9 +118,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(UpdateField command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchemaField.CanUpdate(s.Schema, command); + GuardSchemaField.CanUpdate(s.State.SchemaDef, command); s.UpdateField(command); }); @@ -129,9 +128,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(ReorderFields command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanReorder(s.Schema, command); + GuardSchema.CanReorder(s.State.SchemaDef, command); s.Reorder(command); }); @@ -139,9 +138,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(UpdateSchema command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanUpdate(s.Schema, command); + GuardSchema.CanUpdate(s.State.SchemaDef, command); s.Update(command); }); @@ -149,9 +148,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(PublishSchema command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanPublish(s.Schema, command); + GuardSchema.CanPublish(s.State.SchemaDef, command); s.Publish(command); }); @@ -159,9 +158,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(UnpublishSchema command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanUnpublish(s.Schema, command); + GuardSchema.CanUnpublish(s.State.SchemaDef, command); s.Unpublish(command); }); @@ -169,9 +168,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(ConfigureScripts command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanConfigureScripts(s.Schema, command); + GuardSchema.CanConfigureScripts(s.State.SchemaDef, command); s.ConfigureScripts(command); }); @@ -179,9 +178,9 @@ namespace Squidex.Domain.Apps.Write.Schemas protected Task On(DeleteSchema command, CommandContext context) { - return handler.UpdateAsync(context, s => + return handler.UpdateSyncedAsync(context, s => { - GuardSchema.CanDelete(s.Schema, command); + GuardSchema.CanDelete(s.State.SchemaDef, command); s.Delete(command); }); diff --git a/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs similarity index 64% rename from src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs index 3c264b974..ec377f593 100644 --- a/src/Squidex.Domain.Apps.Write/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaDomainObject.cs @@ -9,121 +9,32 @@ using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Events.Schemas.Utils; -using Squidex.Domain.Apps.Write.Schemas.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Write.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { - public class SchemaDomainObject : DomainObjectBase + public sealed class SchemaDomainObject : DomainObjectBase { private readonly FieldRegistry registry; - private bool isDeleted; - private long totalFields; - private Schema schema; - public Schema Schema - { - get { return schema; } - } - - public bool IsDeleted - { - get { return isDeleted; } - } - - public SchemaDomainObject(Guid id, int version, FieldRegistry registry) - : base(id, version) + public SchemaDomainObject(FieldRegistry registry) { Guard.NotNull(registry, nameof(registry)); this.registry = registry; } - protected void On(SchemaCreated @event) - { - totalFields += @event.Fields?.Count ?? 0; - - schema = SchemaEventDispatcher.Create(@event, registry); - } - - public void On(FieldAdded @event) - { - totalFields++; - - schema = schema.Apply(@event, registry); - } - - protected void On(FieldUpdated @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldLocked @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldHidden @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldShown @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldDisabled @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldEnabled @event) - { - schema = schema.Apply(@event); - } - - protected void On(SchemaUpdated @event) - { - schema = schema.Apply(@event); - } - - protected void On(FieldDeleted @event) - { - schema = schema.Apply(@event); - } - - protected void On(SchemaFieldsReordered @event) - { - schema = schema.Apply(@event); - } - - protected void On(SchemaPublished @event) - { - schema = schema.Apply(@event); - } - - protected void On(SchemaUnpublished @event) - { - schema = schema.Apply(@event); - } - - protected void On(SchemaDeleted @event) - { - isDeleted = true; - } - public SchemaDomainObject Create(CreateSchema command) { VerifyNotCreated(); - var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = new NamedId(Id, command.Name) }); + var @event = SimpleMapper.Map(command, new SchemaCreated { SchemaId = new NamedId(command.SchemaId, command.Name) }); if (command.Fields != null) { @@ -146,7 +57,7 @@ namespace Squidex.Domain.Apps.Write.Schemas { VerifyCreatedAndNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(totalFields + 1, command.Name) })); + RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(State.TotalFields + 1, command.Name) })); return this; } @@ -268,11 +179,11 @@ namespace Squidex.Domain.Apps.Write.Schemas return this; } - protected void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) + private void RaiseEvent(FieldCommand fieldCommand, FieldEvent @event) { SimpleMapper.Map(fieldCommand, @event); - if (schema.FieldsById.TryGetValue(fieldCommand.FieldId, out var field)) + if (State.SchemaDef.FieldsById.TryGetValue(fieldCommand.FieldId, out var field)) { @event.FieldId = new NamedId(field.Id, field.Name); } @@ -282,7 +193,7 @@ namespace Squidex.Domain.Apps.Write.Schemas private void VerifyNotCreated() { - if (schema != null) + if (State.SchemaDef != null) { throw new DomainException("Schema has already been created."); } @@ -290,15 +201,15 @@ namespace Squidex.Domain.Apps.Write.Schemas private void VerifyCreatedAndNotDeleted() { - if (isDeleted || schema == null) + if (State.IsDeleted || State.SchemaDef == null) { throw new DomainException("Schema has already been deleted or not created yet."); } } - protected override void DispatchEvent(Envelope @event) + protected override void OnRaised(Envelope @event) { - this.DispatchAction(@event.Payload); + UpdateState(State.Apply(@event, registry)); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs similarity index 96% rename from src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs rename to src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs index 426d97473..43e1a2328 100644 --- a/src/Squidex.Domain.Apps.Read/Schemas/SchemaHistoryEventsCreator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/SchemaHistoryEventsCreator.cs @@ -7,13 +7,13 @@ // ========================================================================== using System.Threading.Tasks; +using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Read.History; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Read.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { public sealed class SchemaHistoryEventsCreator : HistoryEventsCreatorBase { diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs new file mode 100644 index 000000000..527c584ac --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -0,0 +1,196 @@ +// ========================================================================== +// SchemaState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using Newtonsoft.Json; +using Squidex.Domain.Apps.Core; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Schemas; +using Squidex.Infrastructure.Dispatching; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Reflection; + +namespace Squidex.Domain.Apps.Entities.Schemas.State +{ + public class SchemaState : DomainObjectState, + ISchemaEntity, + IUpdateableEntityWithAppRef, + IUpdateableEntityWithCreatedBy, + IUpdateableEntityWithLastModifiedBy + { + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public Guid AppId { get; set; } + + [JsonProperty] + public int TotalFields { get; set; } = 0; + + [JsonProperty] + public bool IsDeleted { get; set; } + + [JsonProperty] + public string ScriptQuery { get; set; } + + [JsonProperty] + public string ScriptCreate { get; set; } + + [JsonProperty] + public string ScriptUpdate { get; set; } + + [JsonProperty] + public string ScriptDelete { get; set; } + + [JsonProperty] + public string ScriptChange { get; set; } + + [JsonProperty] + public Schema SchemaDef { get; set; } + + [JsonIgnore] + public bool IsPublished + { + get { return SchemaDef.IsPublished; } + } + + protected void On(SchemaCreated @event, FieldRegistry registry) + { + Name = @event.Name; + + var schema = new Schema(@event.Name); + + if (@event.Properties != null) + { + schema = schema.Update(@event.Properties); + } + + if (@event.Fields != null) + { + foreach (var eventField in @event.Fields) + { + TotalFields++; + + var partitioning = + string.Equals(eventField.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? + Partitioning.Language : + Partitioning.Invariant; + + var field = registry.CreateField(TotalFields, eventField.Name, partitioning, eventField.Properties); + + if (eventField.IsHidden) + { + field = field.Hide(); + } + + if (eventField.IsDisabled) + { + field = field.Disable(); + } + + if (eventField.IsLocked) + { + field = field.Lock(); + } + + schema = schema.AddField(field); + } + } + + SchemaDef = schema; + } + + protected void On(FieldAdded @event, FieldRegistry registry) + { + var partitioning = + string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? + Partitioning.Language : + Partitioning.Invariant; + + var field = registry.CreateField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties); + + SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id); + SchemaDef = SchemaDef.AddField(field); + + TotalFields++; + } + + protected void On(SchemaPublished @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.Publish(); + } + + protected void On(SchemaUnpublished @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.Unpublish(); + } + + protected void On(SchemaUpdated @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.Update(@event.Properties); + } + + protected void On(SchemaFieldsReordered @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.ReorderFields(@event.FieldIds); + } + + protected void On(FieldUpdated @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.UpdateField(@event.FieldId.Id, @event.Properties); + } + + protected void On(FieldLocked @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.LockField(@event.FieldId.Id); + } + + protected void On(FieldDisabled @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.DisableField(@event.FieldId.Id); + } + + protected void On(FieldEnabled @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.EnableField(@event.FieldId.Id); + } + + protected void On(FieldHidden @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.HideField(@event.FieldId.Id); + } + + protected void On(FieldShown @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.ShowField(@event.FieldId.Id); + } + + protected void On(FieldDeleted @event, FieldRegistry registry) + { + SchemaDef = SchemaDef.DeleteField(@event.FieldId.Id); + } + + protected void On(SchemaDeleted @event, FieldRegistry registry) + { + IsDeleted = true; + } + + protected void On(ScriptsConfigured @event, FieldRegistry registry) + { + SimpleMapper.Map(@event, this); + } + + public SchemaState Apply(Envelope @event, FieldRegistry registry) + { + var payload = (SquidexEvent)@event.Payload; + + return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload, registry)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Write/Squidex.Domain.Apps.Write.csproj b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj similarity index 90% rename from src/Squidex.Domain.Apps.Write/Squidex.Domain.Apps.Write.csproj rename to src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 54328c3a8..d273d2afb 100644 --- a/src/Squidex.Domain.Apps.Write/Squidex.Domain.Apps.Write.csproj +++ b/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -11,7 +11,6 @@ - diff --git a/src/Squidex.Domain.Apps.Write/SquidexCommand.cs b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs similarity index 85% rename from src/Squidex.Domain.Apps.Write/SquidexCommand.cs rename to src/Squidex.Domain.Apps.Entities/SquidexCommand.cs index 09507a691..c22d7861d 100644 --- a/src/Squidex.Domain.Apps.Write/SquidexCommand.cs +++ b/src/Squidex.Domain.Apps.Entities/SquidexCommand.cs @@ -9,12 +9,12 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Write +namespace Squidex.Domain.Apps.Entities { public abstract class SquidexCommand : ICommand { public RefToken Actor { get; set; } - public long? ExpectedVersion { get; set; } + public long ExpectedVersion { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs deleted file mode 100644 index 5da60f86e..000000000 --- a/src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs +++ /dev/null @@ -1,77 +0,0 @@ -// ========================================================================== -// AppEventDispatcher.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Linq; -using Squidex.Domain.Apps.Core.Apps; - -namespace Squidex.Domain.Apps.Events.Apps.Utils -{ - public static class AppEventDispatcher - { - public static AppContributors Apply(this AppContributors contributors, AppContributorRemoved @event) - { - return contributors.Remove(@event.ContributorId); - } - - public static AppContributors Apply(this AppContributors contributors, AppContributorAssigned @event) - { - return contributors.Assign(@event.ContributorId, @event.Permission); - } - - public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageAdded @event) - { - return languagesConfig.Set(new LanguageConfig(@event.Language)); - } - - public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageRemoved @event) - { - return languagesConfig.Remove(@event.Language); - } - - public static AppClients Apply(this AppClients clients, AppClientAttached @event) - { - return clients.Add(@event.Id, @event.Secret); - } - - public static AppClients Apply(this AppClients clients, AppClientRevoked @event) - { - return clients.Revoke(@event.Id); - } - - public static AppClients Apply(this AppClients clients, AppClientRenamed @event) - { - return clients.Rename(@event.Id, @event.Name); - } - - public static AppClients Apply(this AppClients clients, AppClientUpdated @event) - { - return clients.Update(@event.Id, @event.Permission); - } - - public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event) - { - var fallback = @event.Fallback; - - if (fallback != null && fallback.Count > 0) - { - var existingLangauges = languagesConfig.OfType().Select(x => x.Language); - - fallback = fallback.Intersect(existingLangauges).ToList(); - } - - languagesConfig = languagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, fallback)); - - if (@event.IsMaster) - { - languagesConfig = languagesConfig.MakeMaster(@event.Language); - } - - return languagesConfig; - } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs deleted file mode 100644 index e85f4eecf..000000000 --- a/src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs +++ /dev/null @@ -1,45 +0,0 @@ -// ========================================================================== -// RuleEventDispatcher.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Rules; - -namespace Squidex.Domain.Apps.Events.Rules.Utils -{ - public static class RuleEventDispatcher - { - public static Rule Create(RuleCreated @event) - { - return new Rule(@event.Trigger, @event.Action); - } - - public static Rule Apply(this Rule rule, RuleUpdated @event) - { - if (@event.Trigger != null) - { - return rule.Update(@event.Trigger); - } - - if (@event.Action != null) - { - return rule.Update(@event.Action); - } - - return rule; - } - - public static Rule Apply(this Rule rule, RuleEnabled @event) - { - return rule.Enable(); - } - - public static Rule Apply(this Rule rule, RuleDisabled @event) - { - return rule.Disable(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs b/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs deleted file mode 100644 index 7deea8e63..000000000 --- a/src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs +++ /dev/null @@ -1,133 +0,0 @@ -// ========================================================================== -// SchemaEventDispatcher.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Schemas; - -namespace Squidex.Domain.Apps.Events.Schemas.Utils -{ - public static class SchemaEventDispatcher - { - public static Schema Create(SchemaCreated @event, FieldRegistry registry) - { - var schema = new Schema(@event.Name); - - if (@event.Properties != null) - { - schema = schema.Update(@event.Properties); - } - - if (@event.Fields != null) - { - var fieldId = 1; - - foreach (var eventField in @event.Fields) - { - var partitioning = - string.Equals(eventField.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? - Partitioning.Language : - Partitioning.Invariant; - - var field = registry.CreateField(fieldId, eventField.Name, partitioning, eventField.Properties); - - if (eventField.IsHidden) - { - field = field.Hide(); - } - - if (eventField.IsDisabled) - { - field = field.Disable(); - } - - if (eventField.IsLocked) - { - field = field.Lock(); - } - - schema = schema.AddField(field); - - fieldId++; - } - } - - return schema; - } - - public static Schema Apply(this Schema schema, FieldAdded @event, FieldRegistry registry) - { - var partitioning = - string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? - Partitioning.Language : - Partitioning.Invariant; - - var field = registry.CreateField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties); - - schema = schema.DeleteField(@event.FieldId.Id); - schema = schema.AddField(field); - - return schema; - } - - public static Schema Apply(this Schema schema, FieldUpdated @event) - { - return schema.UpdateField(@event.FieldId.Id, @event.Properties); - } - - public static Schema Apply(this Schema schema, FieldLocked @event) - { - return schema.LockField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, FieldHidden @event) - { - return schema.HideField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, FieldShown @event) - { - return schema.ShowField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, FieldDisabled @event) - { - return schema.DisableField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, FieldEnabled @event) - { - return schema.EnableField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, SchemaUpdated @event) - { - return schema.Update(@event.Properties); - } - - public static Schema Apply(this Schema schema, SchemaFieldsReordered @event) - { - return schema.ReorderFields(@event.FieldIds); - } - - public static Schema Apply(this Schema schema, FieldDeleted @event) - { - return schema.DeleteField(@event.FieldId.Id); - } - - public static Schema Apply(this Schema schema, SchemaPublished @event) - { - return schema.Publish(); - } - - public static Schema Apply(this Schema schema, SchemaUnpublished @event) - { - return schema.Unpublish(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj index 9e69912e3..de665699c 100644 --- a/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj +++ b/src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj @@ -11,9 +11,12 @@ + + + diff --git a/src/Squidex.Domain.Apps.Events/SquidexEvent.cs b/src/Squidex.Domain.Apps.Events/SquidexEvent.cs index 729022e8c..2eb7f3119 100644 --- a/src/Squidex.Domain.Apps.Events/SquidexEvent.cs +++ b/src/Squidex.Domain.Apps.Events/SquidexEvent.cs @@ -13,6 +13,8 @@ namespace Squidex.Domain.Apps.Events { public abstract class SquidexEvent : IEvent { + public string Username { get; set; } + public RefToken Actor { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs deleted file mode 100644 index dae9ddcfc..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs +++ /dev/null @@ -1,75 +0,0 @@ -// ========================================================================== -// MongoAssetEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using MongoDB.Bson.Serialization.Attributes; -using Squidex.Domain.Apps.Core.ValidateContent; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Infrastructure; -using Squidex.Infrastructure.MongoDb; - -namespace Squidex.Domain.Apps.Read.MongoDb.Assets -{ - public sealed class MongoAssetEntity : - MongoEntity, - IAssetEntity, - IUpdateableEntityWithVersion, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy, - IUpdateableEntityWithAppRef - { - [BsonRequired] - [BsonElement] - public string MimeType { get; set; } - - [BsonRequired] - [BsonElement] - public string FileName { get; set; } - - [BsonRequired] - [BsonElement] - public long FileSize { get; set; } - - [BsonRequired] - [BsonElement] - public long FileVersion { get; set; } - - [BsonRequired] - [BsonElement] - public bool IsImage { get; set; } - - [BsonRequired] - [BsonElement] - public long Version { get; set; } - - [BsonRequired] - [BsonElement] - public int? PixelWidth { get; set; } - - [BsonRequired] - [BsonElement] - public int? PixelHeight { get; set; } - - [BsonRequired] - [BsonElement] - public Guid AppId { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken CreatedBy { get; set; } - - [BsonRequired] - [BsonElement] - public RefToken LastModifiedBy { get; set; } - - Guid IAssetInfo.AssetId - { - get { return Id; } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs deleted file mode 100644 index b05fe5d41..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository_EventHandling.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ========================================================================== -// MongoAssetRepository_EventHandling.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using MongoDB.Driver; -using Squidex.Domain.Apps.Events.Assets; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Read.MongoDb.Assets -{ - public partial class MongoAssetRepository - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^asset-"; } - } - - public Task On(Envelope @event) - { - return this.DispatchActionAsync(@event.Payload, @event.Headers); - } - - protected Task On(AssetCreated @event, EnvelopeHeaders headers) - { - return Collection.CreateAsync(@event, headers, a => - { - SimpleMapper.Map(@event, a); - }); - } - - protected Task On(AssetUpdated @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, a => - { - SimpleMapper.Map(@event, a); - }); - } - - protected Task On(AssetRenamed @event, EnvelopeHeaders headers) - { - return Collection.UpdateAsync(@event, headers, a => - { - SimpleMapper.Map(@event, a); - }); - } - - protected Task On(AssetDeleted @event, EnvelopeHeaders headers) - { - return Collection.DeleteOneAsync(x => x.Id == @event.AssetId); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs deleted file mode 100644 index 9f1b3b9fb..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs +++ /dev/null @@ -1,193 +0,0 @@ -// ========================================================================== -// MongoContentRepository.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.OData.UriParser; -using MongoDB.Bson; -using MongoDB.Driver; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Contents; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Domain.Apps.Read.MongoDb.Contents -{ - public partial class MongoContentRepository : IContentRepository, IEventConsumer - { - private const string Prefix = "Projections_Content_"; - private readonly IMongoDatabase database; - private readonly IAppProvider appProvider; - - protected static FilterDefinitionBuilder Filter - { - get - { - return Builders.Filter; - } - } - - protected static UpdateDefinitionBuilder Update - { - get - { - return Builders.Update; - } - } - - protected static ProjectionDefinitionBuilder Projection - { - get - { - return Builders.Projection; - } - } - - protected static IndexKeysDefinitionBuilder Index - { - get - { - return Builders.IndexKeys; - } - } - - public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) - { - Guard.NotNull(database, nameof(database)); - Guard.NotNull(appProvider, nameof(appProvider)); - - this.database = database; - this.appProvider = appProvider; - } - - public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) - { - var collection = GetCollection(app.Id); - - IFindFluent cursor; - try - { - cursor = - collection - .Find(odataQuery, schema.Id, schema.SchemaDef, status) - .Take(odataQuery) - .Skip(odataQuery) - .Sort(odataQuery, schema.SchemaDef); - } - catch (NotSupportedException) - { - throw new ValidationException("This odata operation is not supported."); - } - catch (NotImplementedException) - { - throw new ValidationException("This odata operation is not supported."); - } - - var contentEntities = await cursor.ToListAsync(); - - foreach (var entity in contentEntities) - { - entity.ParseData(schema.SchemaDef); - } - - return contentEntities; - } - - public Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) - { - var collection = GetCollection(app.Id); - - IFindFluent cursor; - try - { - cursor = collection.Find(odataQuery, schema.Id, schema.SchemaDef, status); - } - catch (NotSupportedException) - { - throw new ValidationException("This odata operation is not supported."); - } - catch (NotImplementedException) - { - throw new ValidationException("This odata operation is not supported."); - } - - return cursor.CountAsync(); - } - - public async Task CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) - { - var collection = GetCollection(app.Id); - - var contentsCount = - await collection.Find(x => ids.Contains(x.Id)) - .CountAsync(); - - return contentsCount; - } - - public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids) - { - var collection = GetCollection(app.Id); - - var contentEntities = - await collection.Find(x => ids.Contains(x.Id)) - .ToListAsync(); - - foreach (var entity in contentEntities) - { - entity.ParseData(schema.SchemaDef); - } - - return contentEntities.OfType().ToList(); - } - - public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList contentIds) - { - var collection = GetCollection(appId); - - var contentEntities = - await collection.Find(x => contentIds.Contains(x.Id) && x.AppId == appId).Project(Projection.Include(x => x.Id)) - .ToListAsync(); - - return contentIds.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList(); - } - - public async Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id) - { - var collection = GetCollection(app.Id); - - var contentEntity = - await collection.Find(x => x.Id == id) - .FirstOrDefaultAsync(); - - contentEntity?.ParseData(schema.SchemaDef); - - return contentEntity; - } - - private async Task ForSchemaAsync(NamedId appId, Guid schemaId, Func, ISchemaEntity, Task> action) - { - var collection = GetCollection(appId.Id); - - var schema = await appProvider.GetSchemaAsync(appId.Name, schemaId, true); - - if (schema == null) - { - return; - } - - await action(collection, schema); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs b/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs deleted file mode 100644 index 85acec317..000000000 --- a/src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs +++ /dev/null @@ -1,160 +0,0 @@ -// ========================================================================== -// MongoContentRepository_EventHandling.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using MongoDB.Driver; -using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Events.Assets; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Read.MongoDb.Contents -{ - public partial class MongoContentRepository - { - public string Name - { - get { return GetType().Name; } - } - - public string EventsFilter - { - get { return "^(content-)|(app-)|(asset-)"; } - } - - public async Task ClearAsync() - { - using (var collections = await database.ListCollectionsAsync()) - { - while (await collections.MoveNextAsync()) - { - foreach (var collection in collections.Current) - { - var name = collection["name"].ToString(); - - if (name.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) - { - await database.DropCollectionAsync(name); - } - } - } - } - } - - public Task On(Envelope @event) - { - return this.DispatchActionAsync(@event.Payload, @event.Headers); - } - - protected Task On(AppCreated @event, EnvelopeHeaders headers) - { - return ForAppIdAsync(@event.AppId.Id, async collection => - { - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.SchemaId).Descending(x => x.LastModified)); - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ReferencedIds)); - await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Status)); - await collection.Indexes.CreateOneAsync(Index.Text(x => x.DataText)); - }); - } - - protected Task On(ContentCreated @event, EnvelopeHeaders headers) - { - return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) => - { - return collection.CreateAsync(@event, headers, content => - { - content.SchemaId = @event.SchemaId.Id; - - SimpleMapper.Map(@event, content); - - var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); - - content.DataText = idData?.ToFullText(); - content.IdData = idData; - content.ReferencedIds = idData?.ToReferencedIds(schema.SchemaDef); - }); - }); - } - - protected Task On(ContentUpdated @event, EnvelopeHeaders headers) - { - return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) => - { - var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); - - return collection.UpdateOneAsync( - Filter.Eq(x => x.Id, @event.ContentId), - Update - .Set(x => x.DataText, idData.ToFullText()) - .Set(x => x.IdData, idData) - .Set(x => x.ReferencedIds, idData.ToReferencedIds(schema.SchemaDef)) - .Set(x => x.LastModified, headers.Timestamp()) - .Set(x => x.LastModifiedBy, @event.Actor) - .Set(x => x.Version, headers.EventStreamNumber())); - }); - } - - protected Task On(ContentStatusChanged @event, EnvelopeHeaders headers) - { - return ForAppIdAsync(@event.AppId.Id, collection => - { - return collection.UpdateOneAsync( - Filter.Eq(x => x.Id, @event.ContentId), - Update - .Set(x => x.Status, @event.Status) - .Set(x => x.LastModified, headers.Timestamp()) - .Set(x => x.LastModifiedBy, @event.Actor) - .Set(x => x.Version, headers.EventStreamNumber())); - }); - } - - protected Task On(AssetDeleted @event, EnvelopeHeaders headers) - { - return ForAppIdAsync(@event.AppId.Id, collection => - { - return collection.UpdateManyAsync( - Filter.And( - Filter.AnyEq(x => x.ReferencedIds, @event.AssetId), - Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.AssetId)), - Update.AddToSet(x => x.ReferencedIdsDeleted, @event.AssetId)); - }); - } - - protected Task On(ContentDeleted @event, EnvelopeHeaders headers) - { - return ForAppIdAsync(@event.AppId.Id, async collection => - { - await collection.UpdateManyAsync( - Filter.And( - Filter.AnyEq(x => x.ReferencedIds, @event.ContentId), - Filter.AnyNe(x => x.ReferencedIdsDeleted, @event.ContentId)), - Update.AddToSet(x => x.ReferencedIdsDeleted, @event.ContentId)); - - await collection.DeleteOneAsync(x => x.Id == @event.ContentId); - }); - } - - private Task ForAppIdAsync(Guid appId, Func, Task> action) - { - var collection = GetCollection(appId); - - return action(collection); - } - - private IMongoCollection GetCollection(Guid appId) - { - var name = $"{Prefix}{appId}"; - - return database.GetCollection(name); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/EntityMapper.cs b/src/Squidex.Domain.Apps.Read/EntityMapper.cs deleted file mode 100644 index 829ea277c..000000000 --- a/src/Squidex.Domain.Apps.Read/EntityMapper.cs +++ /dev/null @@ -1,90 +0,0 @@ -// ========================================================================== -// EntityMapper.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Events; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Domain.Apps.Read -{ - public static class EntityMapper - { - public static T Create(SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity, new() - { - var entity = new T(); - - SetId(headers, entity); - - SetVersion(headers, entity); - SetCreated(headers, entity); - SetCreatedBy(@event, entity); - - SetAppId(@event, entity); - - return entity.Update(@event, headers, updater); - } - - public static T Update(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action updater = null) where T : IEntity, new() - { - SetVersion(headers, entity); - SetLastModified(headers, entity); - SetLastModifiedBy(@event, entity); - - updater?.Invoke(entity); - - return entity; - } - - private static void SetId(EnvelopeHeaders headers, IEntity entity) - { - entity.Id = headers.AggregateId(); - } - - private static void SetCreated(EnvelopeHeaders headers, IEntity entity) - { - entity.Created = headers.Timestamp(); - } - - private static void SetLastModified(EnvelopeHeaders headers, IEntity entity) - { - entity.LastModified = headers.Timestamp(); - } - - private static void SetVersion(EnvelopeHeaders headers, IEntity entity) - { - if (entity is IUpdateableEntityWithVersion withVersion) - { - withVersion.Version = headers.EventStreamNumber(); - } - } - - private static void SetCreatedBy(SquidexEvent @event, IEntity entity) - { - if (entity is IUpdateableEntityWithCreatedBy withCreatedBy) - { - withCreatedBy.CreatedBy = @event.Actor; - } - } - - private static void SetLastModifiedBy(SquidexEvent @event, IEntity entity) - { - if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy) - { - withModifiedBy.LastModifiedBy = @event.Actor; - } - } - - private static void SetAppId(SquidexEvent @event, IEntity entity) - { - if (entity is IUpdateableEntityWithAppRef app && @event is AppEvent appEvent) - { - app.AppId = appEvent.AppId.Id; - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/Squidex.Domain.Apps.Read.csproj b/src/Squidex.Domain.Apps.Read/Squidex.Domain.Apps.Read.csproj deleted file mode 100644 index 58922eba3..000000000 --- a/src/Squidex.Domain.Apps.Read/Squidex.Domain.Apps.Read.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - netstandard2.0 - - - full - True - - - - - - - - - - - - - - - - - - ..\..\Squidex.ruleset - - diff --git a/src/Squidex.Domain.Apps.Read/State/AppProvider.cs b/src/Squidex.Domain.Apps.Read/State/AppProvider.cs deleted file mode 100644 index 9a4a155d1..000000000 --- a/src/Squidex.Domain.Apps.Read/State/AppProvider.cs +++ /dev/null @@ -1,87 +0,0 @@ -// ========================================================================== -// AppProvider.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.State.Grains; -using Squidex.Infrastructure; -using Squidex.Infrastructure.States; - -namespace Squidex.Domain.Apps.Read.State -{ - public sealed class AppProvider : IAppProvider - { - private readonly IStateFactory factory; - - public AppProvider(IStateFactory factory) - { - Guard.NotNull(factory, nameof(factory)); - - this.factory = factory; - } - - public async Task GetAppAsync(string appName) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetAppAsync(); - } - - public async Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetAppWithSchemaAsync(id); - } - - public async Task> GetRulesAsync(string appName) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetRulesAsync(); - } - - public async Task GetSchemaAsync(string appName, Guid id, bool provideDeleted = false) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetSchemaAsync(id, provideDeleted); - } - - public async Task GetSchemaAsync(string appName, string name, bool provideDeleted = false) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetSchemaAsync(name, provideDeleted); - } - - public async Task> GetSchemasAsync(string appName) - { - var app = await factory.GetSynchronizedAsync(appName); - - return await app.GetSchemasAsync(); - } - - public async Task> GetUserApps(string userId) - { - var appUser = await factory.GetSynchronizedAsync(userId); - var appNames = await appUser.GetAppNamesAsync(); - - var tasks = appNames.Select(x => GetAppAsync(x)); - - var apps = await Task.WhenAll(tasks); - - return apps.Where(a => a != null && a.Contributors.ContainsKey(userId)).ToList(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs b/src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs deleted file mode 100644 index fd142607b..000000000 --- a/src/Squidex.Domain.Apps.Read/State/AppStateEventConsumer.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// AppStateEventConsumer.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Read.State.Grains; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Read.State -{ - public sealed class AppStateEventConsumer : IEventConsumer - { - private readonly IStateFactory factory; - - public string Name - { - get { return typeof(AppStateEventConsumer).Name; } - } - - public string EventsFilter - { - get { return @"(^app-)|(^schema-)|(^rule\-)"; } - } - - public AppStateEventConsumer(IStateFactory factory) - { - Guard.NotNull(factory, nameof(factory)); - - this.factory = factory; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - - public async Task On(Envelope @event) - { - if (@event.Payload is AppEvent appEvent) - { - var appGrain = await factory.GetSynchronizedAsync(appEvent.AppId.Name); - - await appGrain.HandleAsync(@event); - } - - if (@event.Payload is AppContributorAssigned contributorAssigned) - { - var userGrain = await factory.GetSynchronizedAsync(contributorAssigned.ContributorId); - - await userGrain.AddAppAsync(contributorAssigned.AppId.Name); - } - - if (@event.Payload is AppContributorRemoved contributorRemoved) - { - var userGrain = await factory.GetSynchronizedAsync(contributorRemoved.ContributorId); - - await userGrain.RemoveAppAsync(contributorRemoved.AppId.Name); - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs deleted file mode 100644 index bca5cd380..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs +++ /dev/null @@ -1,134 +0,0 @@ -// ========================================================================== -// AppStateGrain.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public class AppStateGrain : IStatefulObject - { - private readonly FieldRegistry fieldRegistry; - private IPersistence persistence; - private Exception exception; - private AppStateGrainState state; - - public AppStateGrain(FieldRegistry fieldRegistry) - { - Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); - - this.fieldRegistry = fieldRegistry; - } - - public async Task ActivateAsync(string key, IStore store) - { - persistence = store.WithSnapshots(key, s => state = s); - - try - { - await persistence.ReadAsync(); - } - catch (Exception ex) - { - exception = ex; - } - - if (state == null) - { - state = new AppStateGrainState(); - } - - state.SetRegistry(fieldRegistry); - } - - public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id) - { - var schema = state.FindSchema(x => x.Id == id && !x.IsDeleted); - - return Task.FromResult((state.GetApp(), schema)); - } - - public virtual Task GetAppAsync() - { - var result = state.GetApp(); - - return Task.FromResult(result); - } - - public virtual Task> GetRulesAsync() - { - var result = state.FindRules(); - - return Task.FromResult(result); - } - - public virtual Task> GetSchemasAsync() - { - var result = state.FindSchemas(x => !x.IsDeleted); - - return Task.FromResult(result); - } - - public virtual Task GetSchemaAsync(Guid id, bool provideDeleted = false) - { - var result = state.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); - - return Task.FromResult(result); - } - - public virtual Task GetSchemaAsync(string name, bool provideDeleted = false) - { - var result = state.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); - - return Task.FromResult(result); - } - - public async virtual Task HandleAsync(Envelope message) - { - if (exception != null) - { - if (message.Payload is AppCreated) - { - exception = null; - } - else - { - throw exception; - } - } - - if (message.Payload is AppEvent appEvent && (state.App == null || state.App.Id == appEvent.AppId.Id)) - { - try - { - state = state.Apply(message); - - await persistence.WriteSnapshotAsync(state); - } - catch (InconsistentStateException) - { - await persistence.ReadAsync(); - - state = state.Apply(message); - - await persistence.WriteSnapshotAsync(state); - } - } - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState.cs deleted file mode 100644 index 70803d76a..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState.cs +++ /dev/null @@ -1,75 +0,0 @@ -// ========================================================================== -// AppStateGrainState.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Newtonsoft.Json; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Dispatching; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed partial class AppStateGrainState : Cloneable - { - private FieldRegistry registry; - - [JsonProperty] - public JsonAppEntity App { get; set; } - - [JsonProperty] - public ImmutableDictionary Rules { get; set; } = ImmutableDictionary.Empty; - - [JsonProperty] - public ImmutableDictionary Schemas { get; set; } = ImmutableDictionary.Empty; - - public void SetRegistry(FieldRegistry registry) - { - this.registry = registry; - } - - public IAppEntity GetApp() - { - return App; - } - - public ISchemaEntity FindSchema(Func filter) - { - return Schemas?.Values.FirstOrDefault(filter); - } - - public List FindSchemas(Func filter) - { - return Schemas?.Values.Where(filter).OfType().ToList() ?? new List(); - } - - public List FindRules() - { - return Rules?.Values.OfType().ToList() ?? new List(); - } - - public AppStateGrainState Apply(Envelope envelope) - { - return Clone(c => - { - c.DispatchAction(envelope.Payload, envelope.Headers); - - if (c.App != null) - { - c.App.Etag = Guid.NewGuid().ToString(); - } - }); - } - } -} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Apps.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Apps.cs deleted file mode 100644 index 58789f36c..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Apps.cs +++ /dev/null @@ -1,117 +0,0 @@ -// ========================================================================== -// AppStateGrainState_Apps.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Events.Apps.Utils; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed partial class AppStateGrainState - { - public void On(AppCreated @event, EnvelopeHeaders headers) - { - App = EntityMapper.Create(@event, headers, a => - { - SimpleMapper.Map(@event, a); - - a.LanguagesConfig = LanguagesConfig.Build(Language.EN); - }); - } - - public void On(AppLanguageAdded @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.LanguagesConfig = a.LanguagesConfig.Apply(@event); - }); - } - - public void On(AppLanguageRemoved @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.LanguagesConfig = a.LanguagesConfig.Apply(@event); - }); - } - - public void On(AppLanguageUpdated @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.LanguagesConfig = a.LanguagesConfig.Apply(@event); - }); - } - - public void On(AppContributorAssigned @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Contributors = a.Contributors.Apply(@event); - }); - } - - public void On(AppContributorRemoved @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Contributors = a.Contributors.Apply(@event); - }); - } - - public void On(AppClientAttached @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Clients = a.Clients.Apply(@event); - }); - } - - public void On(AppClientUpdated @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Clients = a.Clients.Apply(@event); - }); - } - - public void On(AppClientRenamed @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Clients = a.Clients.Apply(@event); - }); - } - - public void On(AppClientRevoked @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - a.Clients = a.Clients.Apply(@event); - }); - } - - public void On(AppPlanChanged @event, EnvelopeHeaders headers) - { - UpdateApp(@event, headers, a => - { - SimpleMapper.Map(@event, a); - }); - } - - private void UpdateApp(AppEvent @event, EnvelopeHeaders headers, Action updater = null) - { - App = App.Clone().Update(@event, headers, updater); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Rules.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Rules.cs deleted file mode 100644 index 2e3626843..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Rules.cs +++ /dev/null @@ -1,65 +0,0 @@ -// ========================================================================== -// AppStateGrainState_Rules.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Events.Rules; -using Squidex.Domain.Apps.Events.Rules.Utils; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed partial class AppStateGrainState - { - public void On(RuleCreated @event, EnvelopeHeaders headers) - { - var id = @event.RuleId; - - Rules = Rules.SetItem(id, EntityMapper.Create(@event, headers, r => - { - r.RuleDef = RuleEventDispatcher.Create(@event); - })); - } - - public void On(RuleUpdated @event, EnvelopeHeaders headers) - { - UpdateRule(@event, headers, r => - { - r.RuleDef = r.RuleDef.Apply(@event); - }); - } - - public void On(RuleEnabled @event, EnvelopeHeaders headers) - { - UpdateRule(@event, headers, r => - { - r.RuleDef = r.RuleDef.Apply(@event); - }); - } - - public void On(RuleDisabled @event, EnvelopeHeaders headers) - { - UpdateRule(@event, headers, r => - { - r.RuleDef = r.RuleDef.Apply(@event); - }); - } - - public void On(RuleDeleted @event, EnvelopeHeaders headers) - { - Rules = Rules.Remove(@event.RuleId); - } - - private void UpdateRule(RuleEvent @event, EnvelopeHeaders headers, Action updater = null) - { - var id = @event.RuleId; - - Rules = Rules.SetItem(id, x => x.Clone().Update(@event, headers, updater)); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Schemas.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Schemas.cs deleted file mode 100644 index 42ce9ae30..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Schemas.cs +++ /dev/null @@ -1,162 +0,0 @@ -// ========================================================================== -// AppStateGrainState_Schemas.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Events.Schemas.Old; -using Squidex.Domain.Apps.Events.Schemas.Utils; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Reflection; - -#pragma warning disable CS0612 // Type or member is obsolete - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed partial class AppStateGrainState - { - public void On(SchemaCreated @event, EnvelopeHeaders headers) - { - var id = @event.SchemaId.Id; - - Schemas = Schemas.SetItem(id, EntityMapper.Create(@event, headers, s => - { - s.SchemaDef = SchemaEventDispatcher.Create(@event, registry); - - SimpleMapper.Map(@event, s); - })); - } - - public void On(SchemaPublished @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(SchemaUnpublished @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(ScriptsConfigured @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - SimpleMapper.Map(s, @event); - }); - } - - public void On(SchemaUpdated @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(SchemaFieldsReordered @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldAdded @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event, registry); - }); - } - - public void On(FieldUpdated @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldLocked @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldDisabled @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldEnabled @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldHidden @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldShown @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(FieldDeleted @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers, s => - { - s.SchemaDef = s.SchemaDef.Apply(@event); - }); - } - - public void On(WebhookAdded @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers); - } - - public void On(WebhookDeleted @event, EnvelopeHeaders headers) - { - UpdateSchema(@event, headers); - } - - public void On(SchemaDeleted @event, EnvelopeHeaders headers) - { - Schemas = Schemas.Remove(@event.SchemaId.Id); - } - - private void UpdateSchema(SchemaEvent @event, EnvelopeHeaders headers, Action updater = null) - { - var id = @event.SchemaId.Id; - - Schemas = Schemas.SetItem(id, x => x.Clone().Update(@event, headers, updater)); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs deleted file mode 100644 index 08be064e1..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs +++ /dev/null @@ -1,48 +0,0 @@ -// ========================================================================== -// AppUserGrain.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed class AppUserGrain : IStatefulObject - { - private IPersistence persistence; - private AppUserGrainState state = new AppUserGrainState(); - - public Task ActivateAsync(string key, IStore store) - { - persistence = store.WithSnapshots(key, s => state = s); - - return persistence.ReadAsync(); - } - - public Task AddAppAsync(string appName) - { - state = state.AddApp(appName); - - return persistence.WriteSnapshotAsync(state); - } - - public Task RemoveAppAsync(string appName) - { - state = state.RemoveApp(appName); - - return persistence.WriteSnapshotAsync(state); - } - - public Task> GetAppNamesAsync() - { - return Task.FromResult(state.AppNames.ToList()); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrainState.cs b/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrainState.cs deleted file mode 100644 index 10da85cb0..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrainState.cs +++ /dev/null @@ -1,30 +0,0 @@ -// ========================================================================== -// AppUserGrainState.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Immutable; -using Newtonsoft.Json; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed class AppUserGrainState : Cloneable - { - [JsonProperty] - public ImmutableHashSet AppNames { get; set; } = ImmutableHashSet.Empty; - - public AppUserGrainState AddApp(string appName) - { - return Clone(c => c.AppNames = c.AppNames.Add(appName)); - } - - public AppUserGrainState RemoveApp(string appName) - { - return Clone(c => c.AppNames = c.AppNames.Remove(appName)); - } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/JsonAppEntity.cs b/src/Squidex.Domain.Apps.Read/State/Grains/JsonAppEntity.cs deleted file mode 100644 index fbe491584..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/JsonAppEntity.cs +++ /dev/null @@ -1,38 +0,0 @@ -// ========================================================================== -// JsonAppEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Newtonsoft.Json; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed class JsonAppEntity : JsonEntity, IAppEntity - { - [JsonProperty] - public string Name { get; set; } - - [JsonProperty] - public string PlanId { get; set; } - - [JsonProperty] - public string Etag { get; set; } - - [JsonProperty] - public string PlanOwner { get; set; } - - [JsonProperty] - public AppClients Clients { get; set; } = AppClients.Empty; - - [JsonProperty] - public AppContributors Contributors { get; set; } = AppContributors.Empty; - - [JsonProperty] - public LanguagesConfig LanguagesConfig { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/JsonRuleEntity.cs b/src/Squidex.Domain.Apps.Read/State/Grains/JsonRuleEntity.cs deleted file mode 100644 index 287d1b2ab..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/JsonRuleEntity.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ========================================================================== -// JsonRuleEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Newtonsoft.Json; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed class JsonRuleEntity : - JsonEntity, - IRuleEntity, - IUpdateableEntityWithAppRef, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy - { - [JsonProperty] - public Guid AppId { get; set; } - - [JsonProperty] - public RefToken CreatedBy { get; set; } - - [JsonProperty] - public RefToken LastModifiedBy { get; set; } - - [JsonProperty] - public Rule RuleDef { get; set; } - } -} diff --git a/src/Squidex.Domain.Apps.Read/State/Grains/JsonSchemaEntity.cs b/src/Squidex.Domain.Apps.Read/State/Grains/JsonSchemaEntity.cs deleted file mode 100644 index 004467008..000000000 --- a/src/Squidex.Domain.Apps.Read/State/Grains/JsonSchemaEntity.cs +++ /dev/null @@ -1,63 +0,0 @@ -// ========================================================================== -// JsonSchemaEntity.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Newtonsoft.Json; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Infrastructure; - -namespace Squidex.Domain.Apps.Read.State.Grains -{ - public sealed class JsonSchemaEntity : - JsonEntity, - ISchemaEntity, - IUpdateableEntityWithAppRef, - IUpdateableEntityWithCreatedBy, - IUpdateableEntityWithLastModifiedBy - { - [JsonProperty] - public string Name { get; set; } - - [JsonProperty] - public Guid AppId { get; set; } - - [JsonProperty] - public RefToken CreatedBy { get; set; } - - [JsonProperty] - public RefToken LastModifiedBy { get; set; } - - [JsonProperty] - public bool IsDeleted { get; set; } - - [JsonProperty] - public string ScriptQuery { get; set; } - - [JsonProperty] - public string ScriptCreate { get; set; } - - [JsonProperty] - public string ScriptUpdate { get; set; } - - [JsonProperty] - public string ScriptDelete { get; set; } - - [JsonProperty] - public string ScriptChange { get; set; } - - [JsonProperty] - public Schema SchemaDef { get; set; } - - [JsonIgnore] - public bool IsPublished - { - get { return SchemaDef.IsPublished; } - } - } -} diff --git a/src/Squidex.Domain.Apps.Write/Contents/ContentVersionLoader.cs b/src/Squidex.Domain.Apps.Write/Contents/ContentVersionLoader.cs deleted file mode 100644 index 7e6203d5d..000000000 --- a/src/Squidex.Domain.Apps.Write/Contents/ContentVersionLoader.cs +++ /dev/null @@ -1,87 +0,0 @@ -// ========================================================================== -// ContentVersionLoader.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; - -namespace Squidex.Domain.Apps.Write.Contents -{ - public sealed class ContentVersionLoader : IContentVersionLoader - { - private readonly IStreamNameResolver nameResolver; - private readonly IEventStore eventStore; - private readonly IEventDataFormatter formatter; - - public ContentVersionLoader(IEventStore eventStore, IStreamNameResolver nameResolver, IEventDataFormatter formatter) - { - Guard.NotNull(formatter, nameof(formatter)); - Guard.NotNull(eventStore, nameof(eventStore)); - Guard.NotNull(nameResolver, nameof(nameResolver)); - - this.formatter = formatter; - this.eventStore = eventStore; - this.nameResolver = nameResolver; - } - - public async Task LoadAsync(Guid appId, Guid id, long version) - { - var streamName = nameResolver.GetStreamName(typeof(ContentDomainObject), id.ToString()); - - var events = await eventStore.GetEventsAsync(streamName); - - if (events.Count == 0 || events.Count < version - 1) - { - throw new DomainObjectNotFoundException(id.ToString(), typeof(ContentDomainObject)); - } - - NamedContentData contentData = null; - - foreach (var storedEvent in events.Where(x => x.EventStreamNumber <= version)) - { - var envelope = ParseKnownEvent(storedEvent); - - if (envelope != null) - { - if (envelope.Payload is ContentCreated contentCreated) - { - if (contentCreated.AppId.Id != appId) - { - throw new DomainObjectNotFoundException(id.ToString(), typeof(ContentDomainObject)); - } - - contentData = contentCreated.Data; - } - else if (envelope.Payload is ContentUpdated contentUpdated) - { - contentData = contentUpdated.Data; - } - } - } - - return contentData; - } - - private Envelope ParseKnownEvent(StoredEvent storedEvent) - { - try - { - return formatter.Parse(storedEvent.Data); - } - catch (TypeNameNotFoundException) - { - return null; - } - } - } -} diff --git a/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs b/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs index ad7b7c953..c730af9d3 100644 --- a/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs +++ b/src/Squidex.Domain.Users.MongoDb/MongoXmlRepository.cs @@ -18,8 +18,6 @@ namespace Squidex.Domain.Users.MongoDb { public sealed class MongoXmlRepository : MongoRepositoryBase, IXmlRepository { - private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; - public MongoXmlRepository(IMongoDatabase database) : base(database) { diff --git a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj index d9b6e64e1..d61c984d0 100644 --- a/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj +++ b/src/Squidex.Domain.Users.MongoDb/Squidex.Domain.Users.MongoDb.csproj @@ -13,13 +13,13 @@ - + - + ..\..\Squidex.ruleset diff --git a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj index 987174138..d0090e18f 100644 --- a/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj +++ b/src/Squidex.Domain.Users/Squidex.Domain.Users.csproj @@ -16,7 +16,7 @@ - + ..\..\Squidex.ruleset diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs index bc7cda891..cd6659044 100644 --- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs +++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Events/GetEventStore.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; namespace Squidex.Infrastructure.EventSourcing { @@ -90,7 +89,7 @@ namespace Squidex.Infrastructure.EventSourcing public Task AppendEventsAsync(Guid commitId, string streamName, ICollection events) { - return AppendEventsInternalAsync(streamName, ExpectedVersion.Any, events); + return AppendEventsInternalAsync(streamName, EtagVersion.Any, events); } public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection events) diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs index 0d14e580e..7eb6942d8 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEventStore.cs @@ -19,7 +19,6 @@ namespace Squidex.Infrastructure.EventSourcing { public class MongoEventStore : MongoRepositoryBase, IEventStore { - private const long AnyVersion = long.MinValue; private const int MaxAttempts = 20; private static readonly BsonTimestamp EmptyTimestamp = new BsonTimestamp(0); private static readonly FieldDefinition TimestampField = Fields.Build(x => x.Timestamp); @@ -130,12 +129,12 @@ namespace Squidex.Infrastructure.EventSourcing public Task AppendEventsAsync(Guid commitId, string streamName, ICollection events) { - return AppendEventsInternalAsync(commitId, streamName, AnyVersion, events); + return AppendEventsInternalAsync(commitId, streamName, EtagVersion.Any, events); } public Task AppendEventsAsync(Guid commitId, string streamName, long expectedVersion, ICollection events) { - Guard.GreaterEquals(expectedVersion, -1, nameof(expectedVersion)); + Guard.GreaterEquals(expectedVersion, EtagVersion.Any, nameof(expectedVersion)); return AppendEventsInternalAsync(commitId, streamName, expectedVersion, events); } @@ -152,7 +151,7 @@ namespace Squidex.Infrastructure.EventSourcing var currentVersion = await GetEventStreamOffset(streamName); - if (expectedVersion != AnyVersion && expectedVersion != currentVersion) + if (expectedVersion != EtagVersion.Any && expectedVersion != currentVersion) { throw new WrongEventVersionException(currentVersion, expectedVersion); } @@ -175,7 +174,7 @@ namespace Squidex.Infrastructure.EventSourcing { currentVersion = await GetEventStreamOffset(streamName); - if (expectedVersion != AnyVersion) + if (expectedVersion != EtagVersion.Any) { throw new WrongEventVersionException(currentVersion, expectedVersion); } @@ -200,7 +199,7 @@ namespace Squidex.Infrastructure.EventSourcing { var document = await Collection.Find(Filter.Eq(EventStreamField, streamName)) - .Project(Project + .Project(Projection .Include(EventStreamOffsetField) .Include(EventsCountField)) .Sort(Sort.Descending(EventStreamOffsetField)).Limit(1) @@ -211,7 +210,7 @@ namespace Squidex.Infrastructure.EventSourcing return document[nameof(MongoEventCommit.EventStreamOffset)].ToInt64() + document[nameof(MongoEventCommit.EventsCount)].ToInt64(); } - return -1; + return EtagVersion.Empty; } private static FilterDefinition CreateFilter(string streamFilter, StreamPosition streamPosition) diff --git a/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationEntity.cs b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationEntity.cs new file mode 100644 index 000000000..1a7161f68 --- /dev/null +++ b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationEntity.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// MongoMigrationEntity.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Infrastructure.Migrations +{ + public sealed class MongoMigrationEntity + { + [BsonId] + [BsonElement] + [BsonRepresentation(BsonType.String)] + public string Id { get; set; } + + [BsonElement] + [BsonRequired] + public bool IsLocked { get; set; } + + [BsonElement] + [BsonRequired] + public int Version { get; set; } + } +} diff --git a/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs new file mode 100644 index 000000000..b9abb0515 --- /dev/null +++ b/src/Squidex.Infrastructure.MongoDb/Migrations/MongoMigrationStatus.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// MongoMigrationStatus.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using MongoDB.Driver; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Infrastructure.Migrations +{ + public sealed class MongoMigrationStatus : MongoRepositoryBase, IMigrationStatus + { + private const string DefaultId = "Default"; + + public MongoMigrationStatus(IMongoDatabase database) + : base(database) + { + } + + protected override string CollectionName() + { + return "Migration"; + } + + public async Task GetVersionAsync() + { + var entity = await Collection.Find(x => x.Id == DefaultId).FirstOrDefaultAsync(); + + if (entity == null) + { + try + { + await Collection.InsertOneAsync(new MongoMigrationEntity { Id = DefaultId }); + } + catch (MongoWriteException ex) + { + if (ex.WriteError.Category != ServerErrorCategory.DuplicateKey) + { + throw; + } + } + } + + return entity?.Version ?? 0; + } + + public async Task TryLockAsync() + { + var entity = await Collection.FindOneAndUpdateAsync(x => x.Id == DefaultId, Update.Set(x => x.IsLocked, true)); + + return entity?.IsLocked == false; + } + + public Task UnlockAsync(int newVersion) + { + return Collection.UpdateOneAsync(x => x.Id == DefaultId, + Update + .Set(x => x.IsLocked, false) + .Set(x => x.Version, newVersion)); + } + } +} diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 603f41d85..8a8d068bf 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -6,7 +6,10 @@ // All rights reserved. // ========================================================================== +using System; +using System.Linq.Expressions; using System.Threading.Tasks; +using MongoDB.Bson; using MongoDB.Driver; namespace Squidex.Infrastructure.MongoDb @@ -31,5 +34,26 @@ namespace Squidex.Infrastructure.MongoDb return true; } + + public static IFindFluent Only(this IFindFluent find, + Expression> include) + { + return find.Project(Builders.Projection.Include(include)); + } + + public static IFindFluent Only(this IFindFluent find, + Expression> include1, + Expression> include2) + { + return find.Project(Builders.Projection.Include(include1).Include(include2)); + } + + public static IFindFluent Only(this IFindFluent find, + Expression> include1, + Expression> include2, + Expression> include3) + { + return find.Project(Builders.Projection.Include(include1).Include(include2).Include(include3)); + } } } diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs index 929757756..056dd8595 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs @@ -13,18 +13,21 @@ using MongoDB.Bson; using MongoDB.Driver; using Squidex.Infrastructure.Tasks; +#pragma warning disable RECS0108 // Warns about static fields in generic types + namespace Squidex.Infrastructure.MongoDb { public abstract class MongoRepositoryBase : IExternalSystem { private const string CollectionFormat = "{0}Set"; + protected static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; protected static readonly SortDefinitionBuilder Sort = Builders.Sort; protected static readonly UpdateDefinitionBuilder Update = Builders.Update; protected static readonly FieldDefinitionBuilder Fields = FieldDefinitionBuilder.Instance; protected static readonly FilterDefinitionBuilder Filter = Builders.Filter; protected static readonly IndexKeysDefinitionBuilder Index = Builders.IndexKeys; - protected static readonly ProjectionDefinitionBuilder Project = Builders.Projection; + protected static readonly ProjectionDefinitionBuilder Projection = Builders.Projection; private readonly IMongoDatabase mongoDatabase; private Lazy> mongoCollection; diff --git a/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs b/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs index c9d728434..8628d75ec 100644 --- a/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/States/MongoSnapshotStore.cs @@ -6,46 +6,34 @@ // All rights reserved. // ========================================================================== -using System; using System.Threading.Tasks; using MongoDB.Driver; using Newtonsoft.Json; +using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.States { - public sealed class MongoSnapshotStore : ISnapshotStore, IExternalSystem + public class MongoSnapshotStore : MongoRepositoryBase>, ISnapshotStore, IExternalSystem { - private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; - private readonly IMongoDatabase database; private readonly JsonSerializer serializer; public MongoSnapshotStore(IMongoDatabase database, JsonSerializer serializer) + : base(database) { - Guard.NotNull(database, nameof(database)); Guard.NotNull(serializer, nameof(serializer)); - this.database = database; this.serializer = serializer; } - public void Connect() + protected override string CollectionName() { - try - { - database.ListCollections(); - } - catch (Exception ex) - { - throw new ConfigurationException($"MongoDb connection failed to connect to database {database.DatabaseNamespace.DatabaseName}", ex); - } + return $"States_{typeof(T).Name}"; } - public async Task<(T Value, long Version)> ReadAsync(string key) + public async Task<(T Value, long Version)> ReadAsync(TKey key) { - var collection = GetCollection(); - var existing = - await collection.Find(x => x.Id == key) + await Collection.Find(x => Equals(x.Id, key)) .FirstOrDefaultAsync(); if (existing != null) @@ -53,21 +41,15 @@ namespace Squidex.Infrastructure.States return (existing.Doc, existing.Version); } - return (default(T), -1); + return (default(T), EtagVersion.NotFound); } - public async Task WriteAsync(string key, T value, long oldVersion, long newVersion) + public async Task WriteAsync(TKey key, T value, long oldVersion, long newVersion) { - var collection = GetCollection(); - try { - await collection.UpdateOneAsync( - Builders>.Filter.And( - Builders>.Filter.Eq(x => x.Id, key), - Builders>.Filter.Eq(x => x.Version, oldVersion) - ), - Builders>.Update + await Collection.UpdateOneAsync(x => Equals(x.Id, key) && x.Version == oldVersion, + Update .Set(x => x.Doc, value) .Set(x => x.Version, newVersion), Upsert); @@ -77,12 +59,12 @@ namespace Squidex.Infrastructure.States if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { var existingVersion = - await collection.Find(x => x.Id == key) - .Project>(Builders>.Projection.Exclude(x => x.Id)).FirstOrDefaultAsync(); + await Collection.Find(x => Equals(x.Id, key)).Only(x => x.Id, x => x.Version) + .FirstOrDefaultAsync(); if (existingVersion != null) { - throw new InconsistentStateException(existingVersion.Version, oldVersion, ex); + throw new InconsistentStateException(existingVersion["Version"].AsInt64, oldVersion, ex); } } else @@ -91,10 +73,5 @@ namespace Squidex.Infrastructure.States } } } - - private IMongoCollection> GetCollection() - { - return database.GetCollection>($"States_{typeof(T).Name}"); - } } } diff --git a/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs b/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs index 448bb45f6..6f73b1043 100644 --- a/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs +++ b/src/Squidex.Infrastructure.MongoDb/States/MongoState.cs @@ -12,12 +12,12 @@ using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.States { - public sealed class MongoState + public sealed class MongoState { [BsonId] [BsonElement] [BsonRepresentation(BsonType.String)] - public string Id { get; set; } + public TKey Id { get; set; } [BsonRequired] [BsonElement] diff --git a/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs b/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs index 43acf6f5b..2764342be 100644 --- a/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs +++ b/src/Squidex.Infrastructure.MongoDb/UsageTracking/MongoUsageStore.cs @@ -17,8 +17,6 @@ namespace Squidex.Infrastructure.UsageTracking { public sealed class MongoUsageStore : MongoRepositoryBase, IUsageStore { - private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true }; - public MongoUsageStore(IMongoDatabase database) : base(database) { diff --git a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs index 1a9d72b1f..2bd3fbe75 100644 --- a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs +++ b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs @@ -55,7 +55,7 @@ namespace Squidex.Infrastructure { log.LogError(ex, w => w .WriteProperty("action", "PublishRedisMessage") - .WriteProperty("state", "Failed") + .WriteProperty("status", "Failed") .WriteProperty("channel", channelName)); } } @@ -78,7 +78,7 @@ namespace Squidex.Infrastructure log.LogDebug(w => w .WriteProperty("action", "ReceiveRedisMessage") .WriteProperty("channel", channelName) - .WriteProperty("state", "Received")); + .WriteProperty("status", "Received")); } } catch (Exception ex) @@ -86,7 +86,7 @@ namespace Squidex.Infrastructure log.LogError(ex, w => w .WriteProperty("action", "ReceiveRedisMessage") .WriteProperty("channel", channelName) - .WriteProperty("state", "Failed")); + .WriteProperty("status", "Failed")); } } diff --git a/src/Squidex.Infrastructure/Commands/AggregateHandler.cs b/src/Squidex.Infrastructure/Commands/AggregateHandler.cs index 6badd2691..cdfb05cd6 100644 --- a/src/Squidex.Infrastructure/Commands/AggregateHandler.cs +++ b/src/Squidex.Infrastructure/Commands/AggregateHandler.cs @@ -8,51 +8,75 @@ using System; using System.Threading.Tasks; +using Squidex.Infrastructure.Log; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.Commands { public sealed class AggregateHandler : IAggregateHandler { + private readonly AsyncLockPool lockPool = new AsyncLockPool(10000); private readonly IStateFactory stateFactory; + private readonly ISemanticLog log; private readonly IServiceProvider serviceProvider; - public AggregateHandler(IStateFactory stateFactory, IServiceProvider serviceProvider) + public AggregateHandler(IStateFactory stateFactory, IServiceProvider serviceProvider, ISemanticLog log) { Guard.NotNull(stateFactory, nameof(stateFactory)); Guard.NotNull(serviceProvider, nameof(serviceProvider)); + Guard.NotNull(log, nameof(log)); this.stateFactory = stateFactory; this.serviceProvider = serviceProvider; + + this.log = log; } - public Task CreateAsync(CommandContext context, Func creator) where T : class, IAggregate + public Task CreateAsync(CommandContext context, Func creator) where T : class, IDomainObject { Guard.NotNull(creator, nameof(creator)); return InvokeAsync(context, creator, false); } - public Task UpdateAsync(CommandContext context, Func updater) where T : class, IAggregate + public Task UpdateAsync(CommandContext context, Func updater) where T : class, IDomainObject { Guard.NotNull(updater, nameof(updater)); return InvokeAsync(context, updater, true); } - private async Task InvokeAsync(CommandContext context, Func handler, bool isUpdate) where T : class, IAggregate + public Task CreateSyncedAsync(CommandContext context, Func creator) where T : class, IDomainObject + { + Guard.NotNull(creator, nameof(creator)); + + return InvokeSyncedAsync(context, creator, false); + } + + public Task UpdateSyncedAsync(CommandContext context, Func updater) where T : class, IDomainObject + { + Guard.NotNull(updater, nameof(updater)); + + return InvokeSyncedAsync(context, updater, true); + } + + private async Task InvokeAsync(CommandContext context, Func handler, bool isUpdate) where T : class, IDomainObject { Guard.NotNull(context, nameof(context)); - var aggregateCommand = GetCommand(context); - var aggregateFactory = (DomainObjectFactoryFunction)serviceProvider.GetService(typeof(DomainObjectFactoryFunction)); + var domainCommand = GetCommand(context); + var domainObjectId = domainCommand.AggregateId; + var domainObject = await stateFactory.CreateAsync(domainObjectId); - var wrapper = await stateFactory.GetDetachedAsync>(aggregateCommand.AggregateId.ToString()); + if (domainCommand.ExpectedVersion != EtagVersion.Any && domainCommand.ExpectedVersion != domainObject.Version) + { + throw new DomainObjectVersionException(domainObjectId.ToString(), typeof(T), domainObject.Version, domainCommand.ExpectedVersion); + } - var domainObject = aggregateFactory(aggregateCommand.AggregateId); + await handler(domainObject); - await wrapper.LoadAsync(domainObject, isUpdate ? aggregateCommand.ExpectedVersion : -1); - await wrapper.UpdateAsync(handler); + await domainObject.WriteAsync(log); if (!context.IsCompleted) { @@ -62,13 +86,49 @@ namespace Squidex.Infrastructure.Commands } else { - context.Complete(EntityCreatedResult.Create(domainObject.Id, domainObject.Version)); + context.Complete(EntityCreatedResult.Create(domainObjectId, domainObject.Version)); } } return domainObject; } + private async Task InvokeSyncedAsync(CommandContext context, Func handler, bool isUpdate) where T : class, IDomainObject + { + Guard.NotNull(context, nameof(context)); + + var domainCommand = GetCommand(context); + var domainObjectId = domainCommand.AggregateId; + + using (await lockPool.LockAsync(Tuple.Create(typeof(T), domainObjectId))) + { + var domainObject = await stateFactory.GetSingleAsync(domainObjectId); + + if (domainCommand.ExpectedVersion != EtagVersion.Any && domainCommand.ExpectedVersion != domainObject.Version) + { + throw new DomainObjectVersionException(domainObjectId.ToString(), typeof(T), domainObject.Version, domainCommand.ExpectedVersion); + } + + await handler(domainObject); + + await domainObject.WriteAsync(log); + + if (!context.IsCompleted) + { + if (isUpdate) + { + context.Complete(new EntitySavedResult(domainObject.Version)); + } + else + { + context.Complete(EntityCreatedResult.Create(domainObjectId, domainObject.Version)); + } + } + + return domainObject; + } + } + private static IAggregateCommand GetCommand(CommandContext context) { if (!(context.Command is IAggregateCommand command)) diff --git a/src/Squidex.Infrastructure/Commands/CommandExtensions.cs b/src/Squidex.Infrastructure/Commands/CommandExtensions.cs index 86c7640cd..f6d174866 100644 --- a/src/Squidex.Infrastructure/Commands/CommandExtensions.cs +++ b/src/Squidex.Infrastructure/Commands/CommandExtensions.cs @@ -14,24 +14,24 @@ namespace Squidex.Infrastructure.Commands { public static class CommandExtensions { - public static Task CreateAsync(this IAggregateHandler handler, CommandContext context, Action creator) where T : class, IAggregate + public static Task CreateAsync(this IAggregateHandler handler, CommandContext context, Action creator) where T : class, IDomainObject { - return handler.CreateAsync(context, x => - { - creator(x); + return handler.CreateAsync(context, creator.ToAsync()); + } - return TaskHelper.Done; - }); + public static Task UpdateAsync(this IAggregateHandler handler, CommandContext context, Action updater) where T : class, IDomainObject + { + return handler.UpdateAsync(context, updater.ToAsync()); } - public static Task UpdateAsync(this IAggregateHandler handler, CommandContext context, Action updater) where T : class, IAggregate + public static Task CreateSyncedAsync(this IAggregateHandler handler, CommandContext context, Action creator) where T : class, IDomainObject { - return handler.UpdateAsync(context, x => - { - updater(x); + return handler.CreateSyncedAsync(context, creator.ToAsync()); + } - return TaskHelper.Done; - }); + public static Task UpdateSyncedAsync(this IAggregateHandler handler, CommandContext context, Action updater) where T : class, IDomainObject + { + return handler.UpdateSyncedAsync(context, updater.ToAsync()); } public static Task HandleAsync(this ICommandMiddleware commandMiddleware, CommandContext context) diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs b/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs index bd7595cce..840440bde 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectBase.cs @@ -8,85 +8,115 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.States; namespace Squidex.Infrastructure.Commands { - public abstract class DomainObjectBase : IAggregate, IEquatable + public abstract class DomainObjectBase : IDomainObject where T : IDomainState, new() { private readonly List> uncomittedEvents = new List>(); - private readonly Guid id; - private int version; + private Guid id; + private T state; + private IPersistence persistence; - public int Version + public long Version { - get { return version; } + get { return state.Version; } } - public Guid Id + public T State { - get { return id; } + get { return state; } } - protected DomainObjectBase(Guid id, int version) + protected DomainObjectBase() { - Guard.NotEmpty(id, nameof(id)); - Guard.GreaterEquals(version, -1, nameof(version)); - - this.id = id; - - this.version = version; + state = new T(); + state.Version = EtagVersion.Empty; } - protected abstract void DispatchEvent(Envelope @event); - - private void ApplyEventCore(Envelope @event) + public IReadOnlyList> GetUncomittedEvents() { - DispatchEvent(@event); version++; + return uncomittedEvents; } - protected void RaiseEvent(IEvent @event) + public void ClearUncommittedEvents() { - RaiseEvent(Envelope.Create(@event)); + uncomittedEvents.Clear(); } - protected void RaiseEvent(Envelope @event) where TEvent : class, IEvent + public Task ActivateAsync(Guid key, IStore store) { - Guard.NotNull(@event, nameof(@event)); + id = key; - uncomittedEvents.Add(@event.To()); + persistence = store.WithSnapshots(key, s => state = s); - ApplyEventCore(@event.To()); + return persistence.ReadAsync(); } - void IAggregate.ApplyEvent(Envelope @event) + public void RaiseEvent(IEvent @event) { - ApplyEventCore(@event); + RaiseEvent(Envelope.Create(@event)); } - void IAggregate.ClearUncommittedEvents() + public void RaiseEvent(Envelope @event) where TEvent : class, IEvent { - uncomittedEvents.Clear(); + Guard.NotNull(@event, nameof(@event)); + + @event.SetAggregateId(id); + + OnRaised(@event.To()); + + uncomittedEvents.Add(@event.To()); } - public ICollection> GetUncomittedEvents() + public void UpdateState(T newState) { - return uncomittedEvents; + state = newState; } - public override int GetHashCode() + protected virtual void OnRaised(Envelope @event) { - return id.GetHashCode(); } - public override bool Equals(object obj) + public Task WriteStateAsync(long version) { - return Equals(obj as IAggregate); + state.Version = version; + + return persistence.WriteSnapshotAsync(state); } - public bool Equals(IAggregate other) + public async Task WriteAsync(ISemanticLog log) { - return other != null && other.Id.Equals(id); + var events = uncomittedEvents.ToArray(); + + if (events.Length > 0) + { + state.Version += events.Length; + + foreach (var @event in events) + { + @event.SetSnapshotVersion(state.Version); + } + + await persistence.WriteSnapshotAsync(state); + + try + { + await persistence.WriteEventsAsync(events); + } + catch (Exception ex) + { + log.LogFatal(ex, w => w.WriteProperty("action", "writeEvents")); + } + finally + { + uncomittedEvents.Clear(); + } + } } } } diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectWrapper.cs b/src/Squidex.Infrastructure/Commands/DomainObjectWrapper.cs deleted file mode 100644 index 9eb8a7303..000000000 --- a/src/Squidex.Infrastructure/Commands/DomainObjectWrapper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// ========================================================================== -// DomainObjectWrapper.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Linq; -using System.Threading.Tasks; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Infrastructure.Commands -{ - public delegate T DomainObjectFactoryFunction(Guid id) where T : IAggregate; - - public sealed class DomainObjectWrapper : IStatefulObject where T : IAggregate - { - private IPersistence persistence; - private T domainObject; - - public Task ActivateAsync(string key, IStore store) - { - persistence = store.WithEventSourcing(key, e => domainObject.ApplyEvent(e)); - - return TaskHelper.Done; - } - - public Task LoadAsync(T domainObject, long? expectedVersion) - { - this.domainObject = domainObject; - - return persistence.ReadAsync(expectedVersion); - } - - public async Task UpdateAsync(Func handler) - { - await handler(domainObject); - - var events = domainObject.GetUncomittedEvents(); - - foreach (var @event in events) - { - @event.SetAggregateId(domainObject.Id); - } - - await persistence.WriteEventsAsync(events.ToArray()); - - domainObject.ClearUncommittedEvents(); - } - } -} diff --git a/src/Squidex.Infrastructure/Commands/IAggregate.cs b/src/Squidex.Infrastructure/Commands/IAggregate.cs deleted file mode 100644 index abc522e2a..000000000 --- a/src/Squidex.Infrastructure/Commands/IAggregate.cs +++ /dev/null @@ -1,27 +0,0 @@ -// ========================================================================== -// IAggregate.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Infrastructure.Commands -{ - public interface IAggregate - { - Guid Id { get; } - - int Version { get; } - - void ApplyEvent(Envelope @event); - - void ClearUncommittedEvents(); - - ICollection> GetUncomittedEvents(); - } -} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Commands/IAggregateHandler.cs b/src/Squidex.Infrastructure/Commands/IAggregateHandler.cs index 77196fe19..d018ec4dd 100644 --- a/src/Squidex.Infrastructure/Commands/IAggregateHandler.cs +++ b/src/Squidex.Infrastructure/Commands/IAggregateHandler.cs @@ -13,8 +13,12 @@ namespace Squidex.Infrastructure.Commands { public interface IAggregateHandler { - Task CreateAsync(CommandContext context, Func creator) where T : class, IAggregate; + Task CreateAsync(CommandContext context, Func creator) where T : class, IDomainObject; - Task UpdateAsync(CommandContext context, Func updater) where T : class, IAggregate; + Task CreateSyncedAsync(CommandContext context, Func creator) where T : class, IDomainObject; + + Task UpdateAsync(CommandContext context, Func updater) where T : class, IDomainObject; + + Task UpdateSyncedAsync(CommandContext context, Func updater) where T : class, IDomainObject; } } diff --git a/src/Squidex.Infrastructure/Commands/ICommand.cs b/src/Squidex.Infrastructure/Commands/ICommand.cs index f28392eb4..b64b682d4 100644 --- a/src/Squidex.Infrastructure/Commands/ICommand.cs +++ b/src/Squidex.Infrastructure/Commands/ICommand.cs @@ -10,6 +10,6 @@ namespace Squidex.Infrastructure.Commands { public interface ICommand { - long? ExpectedVersion { get; set; } + long ExpectedVersion { get; set; } } } diff --git a/src/Squidex.Infrastructure/Commands/IDomainObject.cs b/src/Squidex.Infrastructure/Commands/IDomainObject.cs new file mode 100644 index 000000000..5877dd2e1 --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/IDomainObject.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// IDomainObjectBase.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.States; + +namespace Squidex.Infrastructure.Commands +{ + public interface IDomainObject : IStatefulObject + { + long Version { get; } + + Task WriteAsync(ISemanticLog log); + } +} \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Commands/IDomainState.cs b/src/Squidex.Infrastructure/Commands/IDomainState.cs new file mode 100644 index 000000000..7cd9bceff --- /dev/null +++ b/src/Squidex.Infrastructure/Commands/IDomainState.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// IDomainState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Infrastructure.Commands +{ + public interface IDomainState + { + long Version { get; set; } + } +} diff --git a/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs b/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs index 7356207cb..f0df32720 100644 --- a/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs +++ b/src/Squidex.Infrastructure/Commands/LogCommandMiddleware.cs @@ -30,13 +30,13 @@ namespace Squidex.Infrastructure.Commands log.LogInformation(w => w .WriteProperty("action", "HandleCommand.") .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Started") + .WriteProperty("status", "Started") .WriteProperty("commandType", context.Command.GetType().Name)); using (log.MeasureInformation(w => w .WriteProperty("action", "HandleCommand.") .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Completed") + .WriteProperty("status", "Completed") .WriteProperty("commandType", context.Command.GetType().Name))) { await next(); @@ -45,7 +45,7 @@ namespace Squidex.Infrastructure.Commands log.LogInformation(w => w .WriteProperty("action", "HandleCommand.") .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Succeeded") + .WriteProperty("status", "Succeeded") .WriteProperty("commandType", context.Command.GetType().Name)); } catch (Exception ex) @@ -53,7 +53,7 @@ namespace Squidex.Infrastructure.Commands log.LogError(ex, w => w .WriteProperty("action", "HandleCommand.") .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Failed") + .WriteProperty("status", "Failed") .WriteProperty("commandType", context.Command.GetType().Name)); throw; @@ -64,7 +64,7 @@ namespace Squidex.Infrastructure.Commands log.LogFatal(w => w .WriteProperty("action", "HandleCommand.") .WriteProperty("actionId", context.ContextId.ToString()) - .WriteProperty("state", "Unhandled") + .WriteProperty("status", "Unhandled") .WriteProperty("commandType", context.Command.GetType().Name)); } } diff --git a/src/Squidex.Infrastructure/EtagVersion.cs b/src/Squidex.Infrastructure/EtagVersion.cs new file mode 100644 index 000000000..d99ec1fd1 --- /dev/null +++ b/src/Squidex.Infrastructure/EtagVersion.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// EtagVersion.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Infrastructure +{ + public static class EtagVersion + { + public const long Any = -2; + + public const long Empty = -1; + + public const long NotFound = long.MinValue; + } +} diff --git a/src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs b/src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs index 276289788..713d3a3da 100644 --- a/src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs +++ b/src/Squidex.Infrastructure/EventSourcing/CommonHeaders.cs @@ -20,6 +20,8 @@ namespace Squidex.Infrastructure.EventSourcing public static readonly string EventStreamNumber = "EventStreamNumber"; + public static readonly string SnapshotVersion = "SnapshotVersion"; + public static readonly string Timestamp = "Timestamp"; public static readonly string Actor = "Actor"; diff --git a/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs b/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs index e1cc4f7b5..fe76d145f 100644 --- a/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs +++ b/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs @@ -26,9 +26,21 @@ namespace Squidex.Infrastructure.EventSourcing return envelope; } + public static long SnapshotVersion(this EnvelopeHeaders headers) + { + return headers[CommonHeaders.SnapshotVersion].ToInt64(CultureInfo.InvariantCulture); + } + + public static Envelope SetSnapshotVersion(this Envelope envelope, long value) where T : class + { + envelope.Headers.Set(CommonHeaders.SnapshotVersion, value); + + return envelope; + } + public static long EventStreamNumber(this EnvelopeHeaders headers) { - return headers[CommonHeaders.EventStreamNumber].ToInt32(CultureInfo.InvariantCulture); + return headers[CommonHeaders.EventStreamNumber].ToInt64(CultureInfo.InvariantCulture); } public static Envelope SetEventStreamNumber(this Envelope envelope, long value) where T : class diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index a40f15a66..346e69345 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -15,7 +15,7 @@ using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.EventSourcing.Grains { - public class EventConsumerGrain : DisposableObjectBase, IStatefulObject, IEventSubscriber + public class EventConsumerGrain : DisposableObjectBase, IStatefulObject, IEventSubscriber { private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStore eventStore; @@ -49,9 +49,9 @@ namespace Squidex.Infrastructure.EventSourcing.Grains } } - public Task ActivateAsync(string key, IStore store) + public Task ActivateAsync(string key, IStore store) { - persistence = store.WithSnapshots(key, s => state = s); + persistence = store.WithSnapshots(key, s => state = s); return persistence.ReadAsync(); } @@ -68,17 +68,17 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public virtual void Stop() { - dispatcher.DispatchAsync(() => HandleStopAsync()).Forget(); + dispatcher.DispatchAsync(HandleStopAsync).Forget(); } public virtual void Start() { - dispatcher.DispatchAsync(() => HandleStartAsync()).Forget(); + dispatcher.DispatchAsync(HandleStartAsync).Forget(); } public virtual void Reset() { - dispatcher.DispatchAsync(() => HandleResetAsync()).Forget(); + dispatcher.DispatchAsync(HandleResetAsync).Forget(); } public virtual void Activate(IEventConsumer eventConsumer) @@ -213,7 +213,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains log.LogFatal(ex, w => w .WriteProperty("action", caller) - .WriteProperty("state", "Failed") + .WriteProperty("status", "Failed") .WriteProperty("eventConsumer", eventConsumer.Name)); state = state.Failed(ex); @@ -229,13 +229,13 @@ namespace Squidex.Infrastructure.EventSourcing.Grains log.LogInformation(w => w .WriteProperty("action", "EventConsumerReset") .WriteProperty("actionId", actionId) - .WriteProperty("state", "Started") + .WriteProperty("status", "Started") .WriteProperty("eventConsumer", eventConsumer.Name)); using (log.MeasureTrace(w => w .WriteProperty("action", "EventConsumerReset") .WriteProperty("actionId", actionId) - .WriteProperty("state", "Completed") + .WriteProperty("status", "Completed") .WriteProperty("eventConsumer", eventConsumer.Name))) { await eventConsumer.ClearAsync(); @@ -250,7 +250,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains log.LogInformation(w => w .WriteProperty("action", "HandleEvent") .WriteProperty("actionId", eventId) - .WriteProperty("state", "Started") + .WriteProperty("status", "Started") .WriteProperty("eventId", eventId) .WriteProperty("eventType", eventType) .WriteProperty("eventConsumer", eventConsumer.Name)); @@ -258,7 +258,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains using (log.MeasureTrace(w => w .WriteProperty("action", "HandleEvent") .WriteProperty("actionId", eventId) - .WriteProperty("state", "Completed") + .WriteProperty("status", "Completed") .WriteProperty("eventId", eventId) .WriteProperty("eventType", eventType) .WriteProperty("eventConsumer", eventConsumer.Name))) diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs index 80e0686a7..70ef98c40 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrainManager.cs @@ -39,7 +39,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains foreach (var consumer in consumers) { - var actor = factory.GetDetachedAsync(consumer.Name).Result; + var actor = factory.CreateAsync(consumer.Name).Result; actors[consumer.Name] = actor; actor.Activate(consumer); diff --git a/src/Squidex.Infrastructure/Language.cs b/src/Squidex.Infrastructure/Language.cs index aedd0f070..f392a2f5c 100644 --- a/src/Squidex.Infrastructure/Language.cs +++ b/src/Squidex.Infrastructure/Language.cs @@ -14,10 +14,10 @@ namespace Squidex.Infrastructure { public sealed partial class Language { - private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$"); + private static readonly Regex CultureRegex = new Regex("^([a-z]{2})(\\-[a-z]{2})?$", RegexOptions.IgnoreCase); + private static readonly Dictionary AllLanguagesField = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly string iso2Code; private readonly string englishName; - private static readonly Dictionary AllLanguagesField = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Language AddLanguage(string iso2Code, string englishName) { @@ -106,7 +106,7 @@ namespace Squidex.Infrastructure return null; } - input = match.Groups[0].Value; + input = match.Groups[1].Value; } if (TryGetLanguage(input.ToLowerInvariant(), out var result)) diff --git a/src/Squidex.Infrastructure/Migrations/IMigration.cs b/src/Squidex.Infrastructure/Migrations/IMigration.cs new file mode 100644 index 000000000..5d6fa7a6a --- /dev/null +++ b/src/Squidex.Infrastructure/Migrations/IMigration.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// IMigration.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Migrations +{ + public interface IMigration + { + int FromVersion { get; } + + int ToVersion { get; } + + Task UpdateAsync(); + } +} diff --git a/src/Squidex.Infrastructure/Migrations/IMigrationStatus.cs b/src/Squidex.Infrastructure/Migrations/IMigrationStatus.cs new file mode 100644 index 000000000..96f7e2043 --- /dev/null +++ b/src/Squidex.Infrastructure/Migrations/IMigrationStatus.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// IMigrationState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Migrations +{ + public interface IMigrationStatus + { + Task GetVersionAsync(); + + Task TryLockAsync(); + + Task UnlockAsync(int newVersion); + } +} diff --git a/src/Squidex.Infrastructure/Migrations/Migrator.cs b/src/Squidex.Infrastructure/Migrations/Migrator.cs new file mode 100644 index 000000000..3ba7e29ce --- /dev/null +++ b/src/Squidex.Infrastructure/Migrations/Migrator.cs @@ -0,0 +1,108 @@ +// ========================================================================== +// Migrator.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.Log; + +namespace Squidex.Infrastructure.Migrations +{ + public sealed class Migrator + { + private readonly IMigrationStatus migrationStatus; + private readonly IEnumerable migrations; + private readonly ISemanticLog log; + + public Migrator(IMigrationStatus migrationStatus, IEnumerable migrations, ISemanticLog log) + { + Guard.NotNull(migrationStatus, nameof(migrationStatus)); + Guard.NotNull(migrations, nameof(migrations)); + Guard.NotNull(log, nameof(log)); + + this.migrationStatus = migrationStatus; + this.migrations = migrations.OrderByDescending(x => x.ToVersion).ToList(); + + this.log = log; + } + + public async Task MigrateAsync() + { + var version = await migrationStatus.GetVersionAsync(); + + var lastMigrator = migrations.FirstOrDefault(); + + if (lastMigrator != null && lastMigrator.ToVersion != version) + { + while (!await migrationStatus.TryLockAsync()) + { + log.LogInformation(w => w + .WriteProperty("action", "Migrate") + .WriteProperty("mesage", "Waiting 5sec to acquire lock.")); + + await Task.Delay(5000); + } + + try + { + var migrationPath = FindMigratorPath(version, lastMigrator.ToVersion).ToList(); + + foreach (var migrator in migrationPath) + { + var name = migrator.GetType().ToString(); + + log.LogInformation(w => w + .WriteProperty("action", "Migration") + .WriteProperty("status", "Started") + .WriteProperty("migrator", name)); + + using (log.MeasureInformation(w => w + .WriteProperty("action", "Migration") + .WriteProperty("status", "Completed") + .WriteProperty("migrator", name))) + { + await migrator.UpdateAsync(); + + version = migrator.ToVersion; + } + } + } + finally + { + await migrationStatus.UnlockAsync(version); + } + } + } + + private IEnumerable FindMigratorPath(int fromVersion, int toVersion) + { + var addedMigrators = new HashSet(); + + while (true) + { + var bestMigrator = migrations.Where(x => x.FromVersion < x.ToVersion).FirstOrDefault(x => x.FromVersion == fromVersion); + + if (bestMigrator != null && addedMigrators.Add(bestMigrator)) + { + fromVersion = bestMigrator.ToVersion; + + yield return bestMigrator; + } + else if (fromVersion != toVersion) + { + throw new InvalidOperationException($"There is no migration path from {fromVersion} to {toVersion}."); + } + else + { + break; + } + } + } + } +} diff --git a/src/Squidex.Infrastructure/States/IPersistence.cs b/src/Squidex.Infrastructure/States/IPersistence.cs index 4a36c48bf..8e9e24ba2 100644 --- a/src/Squidex.Infrastructure/States/IPersistence.cs +++ b/src/Squidex.Infrastructure/States/IPersistence.cs @@ -6,17 +6,24 @@ // All rights reserved. // ========================================================================== +using System.Collections.Generic; using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.States { + public interface IPersistence : IPersistence + { + } + public interface IPersistence { - Task WriteEventsAsync(params Envelope[] @events); + long Version { get; } + + Task WriteEventsAsync(IEnumerable> @events); Task WriteSnapshotAsync(TState state); - Task ReadAsync(long? expectedVersion = null); + Task ReadAsync(long expectedVersion = EtagVersion.Any); } } diff --git a/src/Squidex.Infrastructure/States/ISnapshotStore.cs b/src/Squidex.Infrastructure/States/ISnapshotStore.cs index 8b43eed65..094b375ad 100644 --- a/src/Squidex.Infrastructure/States/ISnapshotStore.cs +++ b/src/Squidex.Infrastructure/States/ISnapshotStore.cs @@ -10,10 +10,10 @@ using System.Threading.Tasks; namespace Squidex.Infrastructure.States { - public interface ISnapshotStore + public interface ISnapshotStore { - Task WriteAsync(string key, T value, long oldVersion, long newVersion); + Task WriteAsync(TKey key, T value, long oldVersion, long newVersion); - Task<(T Value, long Version)> ReadAsync(string key); + Task<(T Value, long Version)> ReadAsync(TKey key); } } diff --git a/src/Squidex.Infrastructure/States/IStateFactory.cs b/src/Squidex.Infrastructure/States/IStateFactory.cs index ff5ab7018..68a99e1a2 100644 --- a/src/Squidex.Infrastructure/States/IStateFactory.cs +++ b/src/Squidex.Infrastructure/States/IStateFactory.cs @@ -6,14 +6,23 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; namespace Squidex.Infrastructure.States { public interface IStateFactory { - Task GetSynchronizedAsync(string key) where T : IStatefulObject; + Task GetSingleAsync(string key) where T : IStatefulObject; - Task GetDetachedAsync(string key) where T : IStatefulObject; + Task GetSingleAsync(Guid key) where T : IStatefulObject; + + Task GetSingleAsync(TKey key) where T : IStatefulObject; + + Task CreateAsync(string key) where T : IStatefulObject; + + Task CreateAsync(Guid key) where T : IStatefulObject; + + Task CreateAsync(TKey key) where T : IStatefulObject; } } diff --git a/src/Squidex.Infrastructure/States/IStatefulObject.cs b/src/Squidex.Infrastructure/States/IStatefulObject.cs index 0c1cbd9cd..b033bb435 100644 --- a/src/Squidex.Infrastructure/States/IStatefulObject.cs +++ b/src/Squidex.Infrastructure/States/IStatefulObject.cs @@ -10,8 +10,8 @@ using System.Threading.Tasks; namespace Squidex.Infrastructure.States { - public interface IStatefulObject + public interface IStatefulObject { - Task ActivateAsync(string key, IStore store); + Task ActivateAsync(TKey key, IStore store); } } diff --git a/src/Squidex.Infrastructure/States/IStore.cs b/src/Squidex.Infrastructure/States/IStore.cs index 72017044c..ab9feb98f 100644 --- a/src/Squidex.Infrastructure/States/IStore.cs +++ b/src/Squidex.Infrastructure/States/IStore.cs @@ -12,12 +12,12 @@ using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.States { - public interface IStore + public interface IStore { - IPersistence WithEventSourcing(string key, Func, Task> applyEvent); + IPersistence WithEventSourcing(TKey key, Func, Task> applyEvent); - IPersistence WithSnapshots(string key, Func applySnapshot); + IPersistence WithSnapshots(TKey key, Func applySnapshot); - IPersistence WithSnapshotsAndEventSourcing(string key, Func applySnapshot, Func, Task> applyEvent); + IPersistence WithSnapshotsAndEventSourcing(TKey key, Func applySnapshot, Func, Task> applyEvent); } } diff --git a/src/Squidex.Infrastructure/States/Persistence.cs b/src/Squidex.Infrastructure/States/Persistence.cs index b4a517616..a306e3327 100644 --- a/src/Squidex.Infrastructure/States/Persistence.cs +++ b/src/Squidex.Infrastructure/States/Persistence.cs @@ -7,179 +7,25 @@ // ========================================================================== using System; -using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure.EventSourcing; +#pragma warning disable RECS0012 // 'if' statement can be re-written as 'switch' statement + namespace Squidex.Infrastructure.States { - public sealed class Persistence : IPersistence + internal sealed class Persistence : Persistence, IPersistence { - private readonly string ownerKey; - private readonly ISnapshotStore snapshotStore; - private readonly IStreamNameResolver streamNameResolver; - private readonly IEventStore eventStore; - private readonly IEventDataFormatter eventDataFormatter; - private readonly Action invalidate; - private readonly Func applyState; - private readonly Func, Task> applyEvent; - private long positionSnapshot = -1; - private long positionEvent = -1; - - public Persistence(string ownerKey, + public Persistence(TKey ownerKey, Action invalidate, + Action failed, IEventStore eventStore, IEventDataFormatter eventDataFormatter, - ISnapshotStore snapshotStore, + ISnapshotStore snapshotStore, IStreamNameResolver streamNameResolver, - Func applyState, Func, Task> applyEvent) + : base(ownerKey, invalidate, failed, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, PersistenceMode.EventSourcing, null, applyEvent) { - Guard.NotNull(ownerKey, nameof(ownerKey)); - - this.ownerKey = ownerKey; - this.applyState = applyState; - this.applyEvent = applyEvent; - this.invalidate = invalidate; - this.eventStore = eventStore; - this.eventDataFormatter = eventDataFormatter; - this.snapshotStore = snapshotStore; - this.streamNameResolver = streamNameResolver; - } - - public async Task ReadAsync(long? expectedVersion) - { - positionSnapshot = -1; - positionEvent = -1; - - if (snapshotStore != null) - { - var (state, position) = await snapshotStore.ReadAsync(ownerKey); - - positionSnapshot = position; - positionEvent = position; - - if (applyState != null && position >= 0) - { - await applyState(state); - } - } - - if (eventStore != null && streamNameResolver != null) - { - var events = await eventStore.GetEventsAsync(GetStreamName(), positionEvent + 1); - - foreach (var @event in events) - { - positionEvent++; - - if (@event.EventStreamNumber != positionEvent) - { - throw new InvalidOperationException("Events must follow the snapshot version in consecutive order with no gaps."); - } - - var parsedEvent = ParseKnownEvent(@event); - - if (parsedEvent != null && applyEvent != null) - { - await applyEvent(parsedEvent); - } - } - } - - var maxVersion = Math.Max(positionEvent, positionSnapshot); - - if (expectedVersion.HasValue && expectedVersion.Value != maxVersion) - { - if (maxVersion == -1) - { - throw new DomainObjectNotFoundException(ownerKey, typeof(TOwner)); - } - else - { - throw new DomainObjectVersionException(ownerKey, typeof(TOwner), maxVersion, expectedVersion.Value); - } - } - } - - public async Task WriteSnapshotAsync(TState state) - { - if (snapshotStore == null) - { - throw new InvalidOperationException("Snapshots are not supported."); - } - - var newPosition = - eventStore != null ? positionEvent : positionSnapshot + 1; - - if (newPosition != positionSnapshot) - { - try - { - await snapshotStore.WriteAsync(ownerKey, state, positionSnapshot, newPosition); - } - catch (InconsistentStateException ex) - { - throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); - } - - positionSnapshot = newPosition; - } - - invalidate(); - } - - public async Task WriteEventsAsync(params Envelope[] @events) - { - Guard.NotNull(events, nameof(@events)); - - if (eventStore == null) - { - throw new InvalidOperationException("Events are not supported."); - } - - if (@events.Length > 0) - { - var commitId = Guid.NewGuid(); - - var eventStream = GetStreamName(); - var eventData = GetEventData(events, commitId); - - try - { - await eventStore.AppendEventsAsync(commitId, GetStreamName(), positionEvent, eventData); - } - catch (WrongEventVersionException ex) - { - throw new DomainObjectVersionException(ownerKey, typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); - } - - positionEvent += events.Length; - } - - invalidate(); - } - - private EventData[] GetEventData(Envelope[] events, Guid commitId) - { - return @events.Select(x => eventDataFormatter.ToEventData(x, commitId, true)).ToArray(); - } - - private string GetStreamName() - { - return streamNameResolver.GetStreamName(typeof(TOwner), ownerKey); - } - - private Envelope ParseKnownEvent(StoredEvent storedEvent) - { - try - { - return eventDataFormatter.Parse(storedEvent.Data); - } - catch (TypeNameNotFoundException) - { - return null; - } } } } diff --git a/src/Squidex.Infrastructure/States/PersistenceMode.cs b/src/Squidex.Infrastructure/States/PersistenceMode.cs new file mode 100644 index 000000000..b1c09fa62 --- /dev/null +++ b/src/Squidex.Infrastructure/States/PersistenceMode.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// PersistenceMode.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +namespace Squidex.Infrastructure.States +{ + public enum PersistenceMode + { + EventSourcing, + Snapshots, + SnapshotsAndEventSourcing + } +} diff --git a/src/Squidex.Infrastructure/States/Persistence{TOwner,TState,TKey}.cs b/src/Squidex.Infrastructure/States/Persistence{TOwner,TState,TKey}.cs new file mode 100644 index 000000000..112b06099 --- /dev/null +++ b/src/Squidex.Infrastructure/States/Persistence{TOwner,TState,TKey}.cs @@ -0,0 +1,253 @@ +// ========================================================================== +// Persistence.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.EventSourcing; + +#pragma warning disable RECS0012 // 'if' statement can be re-written as 'switch' statement + +namespace Squidex.Infrastructure.States +{ + internal class Persistence : IPersistence + { + private readonly TKey ownerKey; + private readonly ISnapshotStore snapshotStore; + private readonly IStreamNameResolver streamNameResolver; + private readonly IEventStore eventStore; + private readonly IEventDataFormatter eventDataFormatter; + private readonly PersistenceMode persistenceMode; + private readonly Action invalidate; + private readonly Action failed; + private readonly Func applyState; + private readonly Func, Task> applyEvent; + private long versionSnapshot = EtagVersion.Empty; + private long versionEvents = EtagVersion.Empty; + private long version; + + public long Version + { + get { return version; } + } + + public Persistence(TKey ownerKey, + Action invalidate, + Action failed, + IEventStore eventStore, + IEventDataFormatter eventDataFormatter, + ISnapshotStore snapshotStore, + IStreamNameResolver streamNameResolver, + PersistenceMode persistenceMode, + Func applyState, + Func, Task> applyEvent) + { + this.ownerKey = ownerKey; + this.applyState = applyState; + this.applyEvent = applyEvent; + this.eventStore = eventStore; + this.eventDataFormatter = eventDataFormatter; + this.invalidate = invalidate; + this.failed = failed; + this.persistenceMode = persistenceMode; + this.snapshotStore = snapshotStore; + this.streamNameResolver = streamNameResolver; + } + + public async Task ReadAsync(long expectedVersion = EtagVersion.Any) + { + versionSnapshot = EtagVersion.Empty; + versionEvents = EtagVersion.Empty; + + await ReadSnapshotAsync(); + await ReadEventsAsync(); + + UpdateVersion(); + + if (expectedVersion != EtagVersion.Any && expectedVersion != version) + { + if (version == EtagVersion.Empty) + { + throw new DomainObjectNotFoundException(ownerKey.ToString(), typeof(TOwner)); + } + else + { + throw new DomainObjectVersionException(ownerKey.ToString(), typeof(TOwner), version, expectedVersion); + } + } + } + + private async Task ReadSnapshotAsync() + { + if (UseSnapshots()) + { + var (state, position) = await snapshotStore.ReadAsync(ownerKey); + + if (position < EtagVersion.Empty) + { + position = EtagVersion.Empty; + } + + versionSnapshot = position; + versionEvents = position; + + if (applyState != null && position >= 0) + { + await applyState(state); + } + } + } + + private async Task ReadEventsAsync() + { + if (UseEventSourcing()) + { + var events = await eventStore.GetEventsAsync(GetStreamName(), versionEvents + 1); + + foreach (var @event in events) + { + versionEvents++; + + if (@event.EventStreamNumber != versionEvents) + { + throw new InvalidOperationException("Events must follow the snapshot version in consecutive order with no gaps."); + } + + var parsedEvent = ParseKnownEvent(@event); + + if (parsedEvent != null && applyEvent != null) + { + await applyEvent(parsedEvent); + } + } + } + } + + public async Task WriteSnapshotAsync(TState state) + { + try + { + var newVersion = UseEventSourcing() ? versionEvents : versionSnapshot + 1; + + if (newVersion != versionSnapshot) + { + try + { + await snapshotStore.WriteAsync(ownerKey, state, versionSnapshot, newVersion); + } + catch (InconsistentStateException ex) + { + throw new DomainObjectVersionException(ownerKey.ToString(), typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); + } + + versionSnapshot = newVersion; + } + + UpdateVersion(); + + invalidate?.Invoke(); + } + catch + { + failed?.Invoke(); + + throw; + } + } + + public async Task WriteEventsAsync(IEnumerable> events) + { + Guard.NotNull(events, nameof(@events)); + + try + { + var eventArray = events.ToArray(); + + if (eventArray.Length > 0) + { + var expectedVersion = UseEventSourcing() ? version : EtagVersion.Any; + + var commitId = Guid.NewGuid(); + + var eventStream = GetStreamName(); + var eventData = GetEventData(eventArray, commitId); + + try + { + await eventStore.AppendEventsAsync(commitId, GetStreamName(), expectedVersion, eventData); + } + catch (WrongEventVersionException ex) + { + throw new DomainObjectVersionException(ownerKey.ToString(), typeof(TOwner), ex.CurrentVersion, ex.ExpectedVersion); + } + + versionEvents += eventArray.Length; + } + + UpdateVersion(); + + invalidate?.Invoke(); + } + catch + { + failed?.Invoke(); + + throw; + } + } + + private EventData[] GetEventData(Envelope[] events, Guid commitId) + { + return @events.Select(x => eventDataFormatter.ToEventData(x, commitId, true)).ToArray(); + } + + private string GetStreamName() + { + return streamNameResolver.GetStreamName(typeof(TOwner), ownerKey.ToString()); + } + + private bool UseSnapshots() + { + return persistenceMode == PersistenceMode.Snapshots || persistenceMode == PersistenceMode.SnapshotsAndEventSourcing; + } + + private bool UseEventSourcing() + { + return persistenceMode == PersistenceMode.EventSourcing || persistenceMode == PersistenceMode.SnapshotsAndEventSourcing; + } + + private Envelope ParseKnownEvent(StoredEvent storedEvent) + { + try + { + return eventDataFormatter.Parse(storedEvent.Data); + } + catch (TypeNameNotFoundException) + { + return null; + } + } + + private void UpdateVersion() + { + if (persistenceMode == PersistenceMode.Snapshots) + { + version = versionSnapshot; + } + else if (persistenceMode == PersistenceMode.EventSourcing) + { + version = versionEvents; + } + else if (persistenceMode == PersistenceMode.SnapshotsAndEventSourcing) + { + version = Math.Max(versionEvents, versionSnapshot); + } + } + } +} diff --git a/src/Squidex.Infrastructure/States/StateFactory.cs b/src/Squidex.Infrastructure/States/StateFactory.cs index a0513c441..abeb95d8a 100644 --- a/src/Squidex.Infrastructure/States/StateFactory.cs +++ b/src/Squidex.Infrastructure/States/StateFactory.cs @@ -21,19 +21,18 @@ namespace Squidex.Infrastructure.States private readonly IPubSub pubSub; private readonly IMemoryCache statesCache; private readonly IServiceProvider services; - private readonly ISnapshotStore snapshotStore; private readonly IStreamNameResolver streamNameResolver; private readonly IEventStore eventStore; private readonly IEventDataFormatter eventDataFormatter; private readonly object lockObject = new object(); private IDisposable pubSubSubscription; - public sealed class ObjectHolder where T : IStatefulObject + public sealed class ObjectHolder where T : IStatefulObject { private readonly Task activationTask; private readonly T obj; - public ObjectHolder(T obj, string key, IStore store) + public ObjectHolder(T obj, TKey key, IStore store) { this.obj = obj; @@ -54,22 +53,19 @@ namespace Squidex.Infrastructure.States IEventStore eventStore, IEventDataFormatter eventDataFormatter, IServiceProvider services, - ISnapshotStore snapshotStore, IStreamNameResolver streamNameResolver) { Guard.NotNull(services, nameof(services)); Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(pubSub, nameof(pubSub)); - Guard.NotNull(snapshotStore, nameof(snapshotStore)); Guard.NotNull(statesCache, nameof(statesCache)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); - this.services = services; this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; this.pubSub = pubSub; - this.snapshotStore = snapshotStore; + this.services = services; this.statesCache = statesCache; this.streamNameResolver = streamNameResolver; } @@ -85,11 +81,21 @@ namespace Squidex.Infrastructure.States }); } - public async Task GetDetachedAsync(string key) where T : IStatefulObject + public Task CreateAsync(string key) where T : IStatefulObject + { + return CreateAsync(key); + } + + public Task CreateAsync(Guid key) where T : IStatefulObject + { + return CreateAsync(key); + } + + public async Task CreateAsync(TKey key) where T : IStatefulObject { Guard.NotNull(key, nameof(key)); - var stateStore = new Store(() => { }, eventStore, eventDataFormatter, snapshotStore, streamNameResolver); + var stateStore = new Store(eventStore, eventDataFormatter, services, streamNameResolver); var state = (T)services.GetService(typeof(T)); await state.ActivateAsync(key, stateStore); @@ -97,25 +103,39 @@ namespace Squidex.Infrastructure.States return state; } - public Task GetSynchronizedAsync(string key) where T : IStatefulObject + public Task GetSingleAsync(string key) where T : IStatefulObject + { + return GetSingleAsync(key); + } + + public Task GetSingleAsync(Guid key) where T : IStatefulObject + { + return GetSingleAsync(key); + } + + public Task GetSingleAsync(TKey key) where T : IStatefulObject { Guard.NotNull(key, nameof(key)); lock (lockObject) { - if (statesCache.TryGetValue>(key, out var stateObj)) + if (statesCache.TryGetValue>(key, out var stateObj)) { return stateObj.ActivateAsync(); } var state = (T)services.GetService(typeof(T)); - var stateStore = new Store(() => - { - pubSub.Publish(new InvalidateMessage { Key = key }, false); - }, eventStore, eventDataFormatter, snapshotStore, streamNameResolver); + var stateStore = new Store(eventStore, eventDataFormatter, services, streamNameResolver, + () => + { + pubSub.Publish(new InvalidateMessage { Key = key.ToString() }, false); + }, () => + { + statesCache.Remove(key); + }); - stateObj = new ObjectHolder(state, key, stateStore); + stateObj = new ObjectHolder(state, key, stateStore); statesCache.CreateEntry(key) .SetValue(stateObj) diff --git a/src/Squidex.Infrastructure/States/Store.cs b/src/Squidex.Infrastructure/States/Store.cs index 0fd3faab9..714fc3472 100644 --- a/src/Squidex.Infrastructure/States/Store.cs +++ b/src/Squidex.Infrastructure/States/Store.cs @@ -12,41 +12,57 @@ using Squidex.Infrastructure.EventSourcing; namespace Squidex.Infrastructure.States { - public sealed class Store : IStore + internal sealed class Store : IStore { private readonly Action invalidate; - private readonly ISnapshotStore snapshotStore; + private readonly Action failed; + private readonly IServiceProvider services; private readonly IStreamNameResolver streamNameResolver; private readonly IEventStore eventStore; private readonly IEventDataFormatter eventDataFormatter; public Store( - Action invalidate, IEventStore eventStore, IEventDataFormatter eventDataFormatter, - ISnapshotStore snapshotStore, - IStreamNameResolver streamNameResolver) + IServiceProvider services, + IStreamNameResolver streamNameResolver, + Action invalidate = null, + Action failed = null) { this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; + this.failed = failed; this.invalidate = invalidate; - this.snapshotStore = snapshotStore; + this.services = services; this.streamNameResolver = streamNameResolver; } - public IPersistence WithEventSourcing(string key, Func, Task> applyEvent) + public IPersistence WithSnapshots(TKey key, Func applySnapshot) { - return new Persistence(key, invalidate, eventStore, eventDataFormatter, null, streamNameResolver, null, applyEvent); + return CreatePersistence(key, PersistenceMode.Snapshots, applySnapshot, null); } - public IPersistence WithSnapshots(string key, Func applySnapshot) + public IPersistence WithSnapshotsAndEventSourcing(TKey key, Func applySnapshot, Func, Task> applyEvent) { - return new Persistence(key, invalidate, null, null, snapshotStore, null, applySnapshot, null); + return CreatePersistence(key, PersistenceMode.SnapshotsAndEventSourcing, applySnapshot, applyEvent); } - public IPersistence WithSnapshotsAndEventSourcing(string key, Func applySnapshot, Func, Task> applyEvent) + public IPersistence WithEventSourcing(TKey key, Func, Task> applyEvent) { - return new Persistence(key, invalidate, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applySnapshot, applyEvent); + Guard.NotDefault(key, nameof(key)); + + var snapshotStore = (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); + + return new Persistence(key, invalidate, failed, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, applyEvent); + } + + private IPersistence CreatePersistence(TKey key, PersistenceMode mode, Func applySnapshot, Func, Task> applyEvent) + { + Guard.NotDefault(key, nameof(key)); + + var snapshotStore = (ISnapshotStore)services.GetService(typeof(ISnapshotStore)); + + return new Persistence(key, invalidate, failed, eventStore, eventDataFormatter, snapshotStore, streamNameResolver, mode, applySnapshot, applyEvent); } } } diff --git a/src/Squidex.Infrastructure/States/StoreExtensions.cs b/src/Squidex.Infrastructure/States/StoreExtensions.cs index 9fd7a8945..53c2fa929 100644 --- a/src/Squidex.Infrastructure/States/StoreExtensions.cs +++ b/src/Squidex.Infrastructure/States/StoreExtensions.cs @@ -14,39 +14,19 @@ namespace Squidex.Infrastructure.States { public static class StoreExtensions { - public static IPersistence WithEventSourcing(this IStore store, string key, Action> applyEvent) + public static IPersistence WithEventSourcing(this IStore store, TKey key, Action> applyEvent) { - return store.WithEventSourcing(key, x => - { - applyEvent(x); - - return TaskHelper.Done; - }); + return store.WithEventSourcing(key, applyEvent.ToAsync()); } - public static IPersistence WithSnapshots(this IStore store, string key, Action applySnapshot) + public static IPersistence WithSnapshots(this IStore store, TKey key, Action applySnapshot) { - return store.WithSnapshots(key, x => - { - applySnapshot(x); - - return TaskHelper.Done; - }); + return store.WithSnapshots(key, applySnapshot.ToAsync()); } - public static IPersistence WithSnapshotsAndEventSourcing(this IStore store, string key, Action applySnapshot, Action> applyEvent) + public static IPersistence WithSnapshotsAndEventSourcing(this IStore store, TKey key, Action applySnapshot, Action> applyEvent) { - return store.WithSnapshotsAndEventSourcing(key, x => - { - applySnapshot(x); - - return TaskHelper.Done; - }, x => - { - applyEvent(x); - - return TaskHelper.Done; - }); + return store.WithSnapshotsAndEventSourcing(key, applySnapshot.ToAsync(), applyEvent.ToAsync()); } } } diff --git a/src/Squidex.Infrastructure/Tasks/AsyncLock.cs b/src/Squidex.Infrastructure/Tasks/AsyncLock.cs new file mode 100644 index 000000000..ad5468ef7 --- /dev/null +++ b/src/Squidex.Infrastructure/Tasks/AsyncLock.cs @@ -0,0 +1,74 @@ +// ========================================================================== +// AsyncLock.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading; +using System.Threading.Tasks; + +#pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body + +namespace Squidex.Infrastructure.Tasks +{ + public sealed class AsyncLock + { + private readonly SemaphoreSlim semaphore; + + public AsyncLock() + { + semaphore = new SemaphoreSlim(1); + } + + public Task LockAsync() + { + Task wait = semaphore.WaitAsync(); + + if (wait.IsCompleted) + { + return Task.FromResult((IDisposable)new LockReleaser(this)); + } + else + { + return wait.ContinueWith(x => (IDisposable)new LockReleaser(this), + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + } + + private class LockReleaser : IDisposable + { + private AsyncLock target; + + internal LockReleaser(AsyncLock target) + { + this.target = target; + } + + public void Dispose() + { + AsyncLock current = target; + + if (current == null) + { + return; + } + + target = null; + + try + { + current.semaphore.Release(); + } + catch + { + // just ignore the Exception + } + } + } + } +} diff --git a/src/Squidex.Infrastructure/Tasks/AsyncLockPool.cs b/src/Squidex.Infrastructure/Tasks/AsyncLockPool.cs new file mode 100644 index 000000000..6701782b2 --- /dev/null +++ b/src/Squidex.Infrastructure/Tasks/AsyncLockPool.cs @@ -0,0 +1,37 @@ +// ========================================================================== +// AsyncLockPool.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace Squidex.Infrastructure.Tasks +{ + public sealed class AsyncLockPool + { + private readonly AsyncLock[] locks; + + public AsyncLockPool(int poolSize) + { + Guard.GreaterThan(poolSize, 0, nameof(poolSize)); + + locks = new AsyncLock[poolSize]; + + for (var i = 0; i < poolSize; i++) + { + locks[i] = new AsyncLock(); + } + } + + public Task LockAsync(object target) + { + Guard.NotNull(target, nameof(target)); + + return locks[Math.Abs(target.GetHashCode() % locks.Length)].LockAsync(); + } + } +} diff --git a/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs b/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs index 944e6db4c..e8cb83234 100644 --- a/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs +++ b/src/Squidex.Infrastructure/Tasks/TaskExtensions.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using System.Threading.Tasks; namespace Squidex.Infrastructure.Tasks @@ -15,5 +16,15 @@ namespace Squidex.Infrastructure.Tasks public static void Forget(this Task task) { } + + public static Func ToAsync(this Action action) + { + return x => + { + action(x); + + return TaskHelper.Done; + }; + } } } diff --git a/src/Squidex/Areas/Api/Controllers/ApiController.cs b/src/Squidex/Areas/Api/Controllers/ApiController.cs index fafbca6e0..66d163d43 100644 --- a/src/Squidex/Areas/Api/Controllers/ApiController.cs +++ b/src/Squidex/Areas/Api/Controllers/ApiController.cs @@ -9,7 +9,7 @@ using System; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; @@ -41,6 +41,11 @@ namespace Squidex.Areas.Api.Controllers get { return App.Name; } } + protected Guid AppId + { + get { return App.Id; } + } + protected ApiController(ICommandBus commandBus) { Guard.NotNull(commandBus, nameof(commandBus)); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 0c2ab7276..59f74abfd 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -12,7 +12,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; @@ -81,7 +82,7 @@ namespace Squidex.Areas.Api.Controllers.Apps await CommandBus.PublishAsync(command); - var response = SimpleMapper.Map(command, new ClientDto { Name = command.Id }); + var response = SimpleMapper.Map(command, new ClientDto { Name = command.Id, Permission = AppClientPermission.Editor }); return CreatedAtAction(nameof(GetClients), new { app }, response); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index c6b6fc22c..3a2faddc5 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Apps.Models; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index 1c499ff2e..4d055de27 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 71aa34709..78cd235c1 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -13,9 +13,9 @@ using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs index 2d5e3b28e..330209f41 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs @@ -11,7 +11,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 7f7313c40..d65cf73f7 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -16,10 +16,10 @@ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Assets.Models; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Write.Assets; -using Squidex.Domain.Apps.Write.Assets.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs index b2db2f78a..6becf0403 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetCreatedDto.cs @@ -8,7 +8,7 @@ using System; using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Write.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure.Commands; namespace Squidex.Areas.Api.Controllers.Assets.Models diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs index a324b9ceb..c038d4f48 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetReplacedDto.cs @@ -7,8 +7,8 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; -using Squidex.Domain.Apps.Write.Assets; -using Squidex.Domain.Apps.Write.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Commands; namespace Squidex.Areas.Api.Controllers.Assets.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentSwaggerController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentSwaggerController.cs index 84a0afd57..a1f54b271 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentSwaggerController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentSwaggerController.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Contents.Generator; -using Squidex.Domain.Apps.Read; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; @@ -47,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(0)] public async Task GetSwagger(string app) { - var schemas = await appProvider.GetSchemasAsync(AppName); + var schemas = await appProvider.GetSchemasAsync(AppId); var swaggerDocument = await schemasSwaggerGenerator.Generate(App, schemas); diff --git a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs index 0f8612fe5..875b451cc 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/ContentsController.cs @@ -16,10 +16,9 @@ using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Contents.Models; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Domain.Apps.Read.Contents; -using Squidex.Domain.Apps.Read.Contents.GraphQL; -using Squidex.Domain.Apps.Write.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; @@ -33,17 +32,14 @@ namespace Squidex.Areas.Api.Controllers.Contents public sealed class ContentsController : ApiController { private readonly IContentQueryService contentQuery; - private readonly IContentVersionLoader contentVersionLoader; private readonly IGraphQLService graphQl; public ContentsController(ICommandBus commandBus, IContentQueryService contentQuery, - IContentVersionLoader contentVersionLoader, IGraphQLService graphQl) : base(commandBus) { this.contentQuery = contentQuery; - this.contentVersionLoader = contentVersionLoader; this.graphQl = graphQl; } @@ -142,13 +138,20 @@ namespace Squidex.Areas.Api.Controllers.Contents [ApiCosts(1)] public async Task GetContentVersion(string name, Guid id, int version) { - var contentData = await contentVersionLoader.LoadAsync(App.Id, id, version); + var content = await contentQuery.FindContentAsync(App, name, User, id, version); - var response = contentData; + var response = SimpleMapper.Map(content.Content, new ContentDto()); + + if (content.Content.Data != null) + { + var isFrontendClient = User.IsFrontendClient(); + + response.Data = content.Content.Data.ToApiModel(content.Schema.SchemaDef, App.LanguagesConfig, !isFrontendClient); + } Response.Headers["ETag"] = new StringValues(version.ToString()); - return Ok(response); + return Ok(response.Data); } [MustBeAppEditor] diff --git a/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemasSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemasSwaggerGenerator.cs index 82295c3a5..c895641f1 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemasSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Generator/SchemasSwaggerGenerator.cs @@ -16,8 +16,8 @@ using NSwag; using NSwag.AspNetCore; using NSwag.SwaggerGeneration; using Squidex.Config; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Pipeline.Swagger; diff --git a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs index 2a048594b..62471547c 100644 --- a/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Content/Models/ContentDto.cs @@ -10,7 +10,7 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs index d0e224275..a4570cf22 100644 --- a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs +++ b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.History.Models; -using Squidex.Domain.Apps.Read.History.Repositories; +using Squidex.Domain.Apps.Entities.History.Repositories; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index 6481585a7..e7a42a3fc 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -12,8 +12,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Plans.Models; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; @@ -62,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Plans { CurrentPlanId = planId, Plans = appPlansProvider.GetAvailablePlans().Select(x => SimpleMapper.Map(x, new PlanDto())).ToList(), - PlanOwner = App.PlanOwner, + PlanOwner = App.Plan?.Owner.Identifier, HasPortal = appPlansBillingManager.HasPortal }; diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleConverter.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleConverter.cs index 3cf4b64ed..072ce5141 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleConverter.cs @@ -7,8 +7,8 @@ // ========================================================================== using System; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs index 1a09e88be..7a973ee52 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs @@ -10,7 +10,7 @@ using System; using System.ComponentModel.DataAnnotations; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; -using Squidex.Domain.Apps.Read.Rules; +using Squidex.Domain.Apps.Entities.Rules; namespace Squidex.Areas.Api.Controllers.Rules.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 5d0e34c75..481e4f309 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -14,9 +14,9 @@ using NodaTime; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Areas.Api.Controllers.Rules.Models.Converters; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Rules.Repositories; -using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; @@ -59,7 +59,7 @@ namespace Squidex.Areas.Api.Controllers.Rules [ApiCosts(1)] public async Task GetRules(string app) { - var rules = await appProvider.GetRulesAsync(AppName); + var rules = await appProvider.GetRulesAsync(AppId); var response = rules.Select(r => r.ToModel()); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/SchemaConverter.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/SchemaConverter.cs index 7233f65bc..dc7ba4c68 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/SchemaConverter.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/SchemaConverter.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 7fbda49ad..40c40429e 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Schemas.Models; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 2e5b8e00f..26ba58ac0 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -15,9 +15,9 @@ using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Schemas.Models; using Squidex.Areas.Api.Controllers.Schemas.Models.Converters; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; @@ -56,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ApiCosts(0)] public async Task GetSchemas(string app) { - var schemas = await appProvider.GetSchemasAsync(AppName); + var schemas = await appProvider.GetSchemasAsync(AppId); var response = schemas.Select(s => s.ToModel()).ToList(); @@ -83,11 +83,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas if (Guid.TryParse(name, out var id)) { - entity = await appProvider.GetSchemaAsync(AppName, id); + entity = await appProvider.GetSchemaAsync(AppId, id); } else { - entity = await appProvider.GetSchemaAsync(AppName, name); + entity = await appProvider.GetSchemaAsync(AppId, name); } if (entity == null || entity.IsDeleted) diff --git a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs index d9895c4b1..d12f17656 100644 --- a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs @@ -12,8 +12,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using NSwag.Annotations; using Squidex.Areas.Api.Controllers.Statistics.Models; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Read.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline; diff --git a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs index 4d86a172b..d384db228 100644 --- a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs +++ b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs @@ -15,7 +15,7 @@ using IdentityServer4.Stores; using Microsoft.Extensions.Options; using Squidex.Config; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; namespace Squidex.Areas.IdentityServer.Config diff --git a/src/Squidex/Areas/Portal/Middlewares/PortalRedirectMiddleware.cs b/src/Squidex/Areas/Portal/Middlewares/PortalRedirectMiddleware.cs index 603f7bb94..a17190981 100644 --- a/src/Squidex/Areas/Portal/Middlewares/PortalRedirectMiddleware.cs +++ b/src/Squidex/Areas/Portal/Middlewares/PortalRedirectMiddleware.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.Security; namespace Squidex.Areas.Portal.Middlewares diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index 5552c561a..b8cd37168 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -19,6 +19,7 @@ using Squidex.Infrastructure.Assets.ImageSharp; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.States; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline; @@ -91,6 +92,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + + services.AddSingletonAs() + .AsSelf(); } } } diff --git a/src/Squidex/Config/Domain/ReadServices.cs b/src/Squidex/Config/Domain/ReadServices.cs index 13f0ff490..54c9958ce 100644 --- a/src/Squidex/Config/Domain/ReadServices.cs +++ b/src/Squidex/Config/Domain/ReadServices.cs @@ -13,23 +13,19 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.Actions; using Squidex.Domain.Apps.Core.HandleRules.Triggers; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Contents; -using Squidex.Domain.Apps.Read.Contents.Edm; -using Squidex.Domain.Apps.Read.Contents.GraphQL; -using Squidex.Domain.Apps.Read.History; -using Squidex.Domain.Apps.Read.Rules; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Read.State; -using Squidex.Domain.Apps.Read.State.Grains; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.Edm; +using Squidex.Domain.Apps.Entities.Contents.GraphQL; +using Squidex.Domain.Apps.Entities.History; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Users; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; -using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.States; @@ -103,15 +99,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .As(); - services.AddSingletonAs(c => - new CompoundEventConsumer(c.GetServices().ToArray())); - services.AddSingletonAs(c => { var allEventConsumers = c.GetServices(); @@ -119,13 +109,11 @@ namespace Squidex.Config.Domain return new EventConsumerFactory(n => allEventConsumers.FirstOrDefault(x => x.Name == n)); }); - services.AddSingletonAs(); - - services.AddTransient(typeof(DomainObjectWrapper<>)); - services.AddTransient(); - services.AddTransient(); + services.AddSingletonAs() + .AsSelf(); - services.AddSingleton(); + services.AddSingletonAs() + .AsSelf(); } } } diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index e95127b89..bbbd00046 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -7,6 +7,7 @@ // ========================================================================== using Microsoft.Extensions.DependencyInjection; +using Migrate_01; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using NodaTime; @@ -26,16 +27,16 @@ namespace Squidex.Config.Domain { public static class SerializationServices { - private static readonly TypeNameRegistry TypeNameRegistry = new TypeNameRegistry(); - private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings(); - private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry); + private static readonly TypeNameRegistry TypeNameRegistry = + new TypeNameRegistry() + .MapUnmapped(typeof(Migration01).Assembly) + .MapUnmapped(typeof(SquidexCoreModel).Assembly) + .MapUnmapped(typeof(SquidexEvents).Assembly) + .MapUnmapped(typeof(SquidexInfrastructure).Assembly); - public static JsonSerializerSettings DefaultJsonSettings - { - get { return SerializerSettings; } - } + private static readonly FieldRegistry FieldRegistry = new FieldRegistry(TypeNameRegistry); - private static void ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) + private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings, TypeNameHandling typeNameHandling) { settings.SerializationBinder = new TypeNameSerializationBinder(TypeNameRegistry); @@ -64,25 +65,21 @@ namespace Squidex.Config.Domain settings.TypeNameHandling = typeNameHandling; settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - } - - static SerializationServices() - { - TypeNameRegistry.MapUnmapped(typeof(SquidexCoreModel).Assembly); - TypeNameRegistry.MapUnmapped(typeof(SquidexEvents).Assembly); - TypeNameRegistry.MapUnmapped(typeof(SquidexInfrastructure).Assembly); - - ConfigureJson(SerializerSettings, TypeNameHandling.Auto); - BsonJsonConvention.Register(JsonSerializer.Create(SerializerSettings)); + return settings; } public static IServiceCollection AddMySerializers(this IServiceCollection services) { - services.AddSingletonAs(t => TypeNameRegistry); + var serializerSettings = ConfigureJson(new JsonSerializerSettings(), TypeNameHandling.Auto); + var serializerInstance = JsonSerializer.Create(serializerSettings); + services.AddSingletonAs(t => FieldRegistry); - services.AddSingletonAs(t => SerializerSettings); - services.AddSingletonAs(t => JsonSerializer.Create(SerializerSettings)); + services.AddSingletonAs(t => serializerSettings); + services.AddSingletonAs(t => serializerInstance); + services.AddSingletonAs(t => TypeNameRegistry); + + BsonJsonConvention.Register(serializerInstance); return services; } diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index 5c76a96e1..ce4fe184d 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using IdentityServer4.Stores; using Microsoft.AspNetCore.DataProtection.Repositories; using Microsoft.AspNetCore.Identity; @@ -13,22 +14,32 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Newtonsoft.Json; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.History; -using Squidex.Domain.Apps.Read.History.Repositories; -using Squidex.Domain.Apps.Read.MongoDb.Assets; -using Squidex.Domain.Apps.Read.MongoDb.Contents; -using Squidex.Domain.Apps.Read.MongoDb.History; -using Squidex.Domain.Apps.Read.MongoDb.Rules; -using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Apps.Repositories; +using Squidex.Domain.Apps.Entities.Apps.State; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Assets.State; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Contents.State; +using Squidex.Domain.Apps.Entities.History; +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.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Rules.State; +using Squidex.Domain.Apps.Entities.Schemas.Repositories; +using Squidex.Domain.Apps.Entities.Schemas.State; using Squidex.Domain.Users; using Squidex.Domain.Users.MongoDb; using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.EventSourcing.Grains; +using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.States; using Squidex.Infrastructure.UsageTracking; using Squidex.Shared.Users; @@ -55,8 +66,12 @@ namespace Squidex.Config.Domain .As() .As(); - services.AddSingletonAs(c => new MongoSnapshotStore(mongoDatabase, c.GetRequiredService())) - .As() + services.AddSingletonAs(c => new MongoMigrationStatus(mongoDatabase)) + .As() + .As(); + + services.AddSingletonAs(c => new MongoSnapshotStore(mongoDatabase, c.GetRequiredService())) + .As>() .As(); services.AddSingletonAs(c => new MongoUserStore(mongoDatabase)) @@ -78,27 +93,44 @@ namespace Squidex.Config.Domain .As() .As(); - services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetService())) - .As() - .As(); - services.AddSingletonAs(c => new MongoRuleEventRepository(mongoDatabase)) .As() .As(); - services.AddSingletonAs(c => new MongoHistoryEventRepository(mongoDatabase, c.GetServices())) - .As() - .As() + services.AddSingletonAs(c => new MongoAppRepository(mongoDatabase)) + .As() + .As>() .As(); services.AddSingletonAs(c => new MongoAssetRepository(mongoDatabase)) .As() - .As() + .As>() + .As(); + + services.AddSingletonAs(c => new MongoRuleRepository(mongoDatabase)) + .As() + .As>() + .As(); + + services.AddSingletonAs(c => new MongoSchemaRepository(mongoDatabase)) + .As() + .As>() + .As(); + + services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetService())) + .As() + .As>() + .As() + .As(); + + services.AddSingletonAs(c => new MongoHistoryEventRepository(mongoDatabase, c.GetServices())) + .As() + .As() .As(); services.AddSingletonAs(c => new MongoAssetStatsRepository(mongoDatabase)) .As() - .As() + .As() .As(); } }); diff --git a/src/Squidex/Config/Domain/SystemExtensions.cs b/src/Squidex/Config/Domain/SystemExtensions.cs index ed8a4bccd..95538ef4c 100644 --- a/src/Squidex/Config/Domain/SystemExtensions.cs +++ b/src/Squidex/Config/Domain/SystemExtensions.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; namespace Squidex.Config.Domain { @@ -24,5 +25,12 @@ namespace Squidex.Config.Domain system.Connect(); } } + + public static void Migrate(this IServiceProvider services) + { + var migrator = services.GetRequiredService(); + + migrator.MigrateAsync().Wait(); + } } } diff --git a/src/Squidex/Config/Domain/WriteServices.cs b/src/Squidex/Config/Domain/WriteServices.cs index b0b7e070d..524c3e121 100644 --- a/src/Squidex/Config/Domain/WriteServices.cs +++ b/src/Squidex/Config/Domain/WriteServices.cs @@ -7,15 +7,16 @@ // ========================================================================== using Microsoft.Extensions.DependencyInjection; -using Squidex.Domain.Apps.Core.Schemas; +using Migrate_01; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Write.Apps; -using Squidex.Domain.Apps.Write.Assets; -using Squidex.Domain.Apps.Write.Contents; -using Squidex.Domain.Apps.Write.Rules; -using Squidex.Domain.Apps.Write.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Users; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Migrations; using Squidex.Pipeline.CommandMiddlewares; namespace Squidex.Config.Domain @@ -30,9 +31,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .As(); @@ -63,17 +61,23 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs>(c => (id => new AppDomainObject(id, -1))); - services.AddSingletonAs>(c => (id => new RuleDomainObject(id, -1))); - services.AddSingletonAs>(c => (id => new AssetDomainObject(id, -1))); - services.AddSingletonAs>(c => (id => new ContentDomainObject(id, -1))); + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .AsSelf(); + + services.AddTransientAs() + .AsSelf(); + + services.AddTransientAs() + .AsSelf(); - services.AddSingletonAs>(c => - { - var fieldRegistry = c.GetRequiredService(); + services.AddTransientAs() + .AsSelf(); - return id => new SchemaDomainObject(id, -1, fieldRegistry); - }); + services.AddTransientAs() + .AsSelf(); } } } diff --git a/src/Squidex/Config/MyUsageOptions.cs b/src/Squidex/Config/MyUsageOptions.cs index 370c4a90b..53c558bad 100644 --- a/src/Squidex/Config/MyUsageOptions.cs +++ b/src/Squidex/Config/MyUsageOptions.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; +using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; namespace Squidex.Config { diff --git a/src/Squidex/Config/ServiceExtensions.cs b/src/Squidex/Config/ServiceExtensions.cs index 61050959f..f2c670304 100644 --- a/src/Squidex/Config/ServiceExtensions.cs +++ b/src/Squidex/Config/ServiceExtensions.cs @@ -25,6 +25,11 @@ namespace Squidex.Config this.services = services; } + public InterfaceRegistrator AsSelf() + { + return this; + } + public InterfaceRegistrator As() { if (typeof(TInterface) != typeof(T)) @@ -39,6 +44,20 @@ namespace Squidex.Config } } + public static InterfaceRegistrator AddTransientAs(this IServiceCollection services, Func factory) where T : class + { + services.AddTransient(typeof(T), factory); + + return new InterfaceRegistrator(services); + } + + public static InterfaceRegistrator AddTransientAs(this IServiceCollection services) where T : class + { + services.AddTransient(); + + return new InterfaceRegistrator(services); + } + public static InterfaceRegistrator AddSingletonAs(this IServiceCollection services, Func factory) where T : class { services.AddSingleton(typeof(T), factory); diff --git a/src/Squidex/Pipeline/ApiCostsFilter.cs b/src/Squidex/Pipeline/ApiCostsFilter.cs index 358c1a6f3..557fc9647 100644 --- a/src/Squidex/Pipeline/ApiCostsFilter.cs +++ b/src/Squidex/Pipeline/ApiCostsFilter.cs @@ -11,7 +11,7 @@ using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Squidex.Domain.Apps.Read.Apps.Services; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.UsageTracking; namespace Squidex.Pipeline diff --git a/src/Squidex/Pipeline/AppApiFilter.cs b/src/Squidex/Pipeline/AppApiFilter.cs index a789893c7..88fa4555a 100644 --- a/src/Squidex/Pipeline/AppApiFilter.cs +++ b/src/Squidex/Pipeline/AppApiFilter.cs @@ -9,8 +9,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Pipeline { diff --git a/src/Squidex/Pipeline/AppPermissionAttribute.cs b/src/Squidex/Pipeline/AppPermissionAttribute.cs index 82517dd38..d6405f89a 100644 --- a/src/Squidex/Pipeline/AppPermissionAttribute.cs +++ b/src/Squidex/Pipeline/AppPermissionAttribute.cs @@ -12,7 +12,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Entities.Apps; using Squidex.Infrastructure.Security; using Squidex.Shared.Identity; diff --git a/src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs index 82029aea4..f2a9ad3f8 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/ETagCommandMiddleware.cs @@ -11,6 +11,7 @@ using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; namespace Squidex.Pipeline.CommandMiddlewares @@ -33,6 +34,10 @@ namespace Squidex.Pipeline.CommandMiddlewares { context.Command.ExpectedVersion = expectedVersion; } + else + { + context.Command.ExpectedVersion = EtagVersion.Any; + } await next(); diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs index 07a24847a..1cda63c8f 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithActorCommandMiddleware.cs @@ -10,7 +10,7 @@ using System; using System.Security; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Squidex.Domain.Apps.Write; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index d5a277039..856af45fe 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -9,7 +9,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Squidex.Domain.Apps.Write; +using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index 596ea587f..b1b0df833 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -9,10 +9,8 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write; -using Squidex.Domain.Apps.Write.Schemas; +using Squidex.Domain.Apps.Entities; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -44,11 +42,11 @@ namespace Squidex.Pipeline.CommandMiddlewares if (Guid.TryParse(schemaName, out var id)) { - schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, id); + schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, id); } else { - schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Name, schemaName); + schema = await appProvider.GetSchemaAsync(schemaCommand.AppId.Id, schemaName); } if (schema == null) diff --git a/src/Squidex/Pipeline/GraphQLUrlGenerator.cs b/src/Squidex/Pipeline/GraphQLUrlGenerator.cs index e6d06026e..5f6b783d3 100644 --- a/src/Squidex/Pipeline/GraphQLUrlGenerator.cs +++ b/src/Squidex/Pipeline/GraphQLUrlGenerator.cs @@ -8,11 +8,11 @@ using Microsoft.Extensions.Options; using Squidex.Config; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Contents; -using Squidex.Domain.Apps.Read.Contents.GraphQL; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Contents.GraphQL; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure.Assets; namespace Squidex.Pipeline diff --git a/src/Squidex/Pipeline/IAppFeature.cs b/src/Squidex/Pipeline/IAppFeature.cs index d85882707..fa2ced5e2 100644 --- a/src/Squidex/Pipeline/IAppFeature.cs +++ b/src/Squidex/Pipeline/IAppFeature.cs @@ -6,7 +6,7 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Read.Apps; +using Squidex.Domain.Apps.Entities.Apps; namespace Squidex.Pipeline { diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index ebc86a383..f60bfd81e 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -29,11 +29,14 @@ + + + - + @@ -41,15 +44,12 @@ - - - - + @@ -61,19 +61,19 @@ - + - + - + - + diff --git a/src/Squidex/WebStartup.cs b/src/Squidex/WebStartup.cs index 2abdbadc3..d88b28055 100644 --- a/src/Squidex/WebStartup.cs +++ b/src/Squidex/WebStartup.cs @@ -39,6 +39,7 @@ namespace Squidex public void Configure(IApplicationBuilder app) { app.ApplicationServices.LogConfiguration(); + app.ApplicationServices.Migrate(); app.ApplicationServices.TestExternalSystems(); app.UseMyCors(); diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 51fcb48ac..a6ebdcd8c 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -56,7 +56,6 @@ export * from './services/analytics.service'; export * from './services/clipboard.service'; export * from './services/dialog.service'; export * from './services/local-store.service'; -export * from './services/local-cache.service'; export * from './services/message-bus.service'; export * from './services/onboarding.service'; export * from './services/resource-loader.service'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 1edc0e5a5..ad1a9b076 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -39,7 +39,6 @@ import { JsonEditorComponent, KeysPipe, KNumberPipe, - LocalCacheService, LocalStoreService, LowerCaseInputDirective, MarkdownEditorComponent, @@ -197,7 +196,6 @@ export class SqxFrameworkModule { CanDeactivateGuard, ClipboardService, DialogService, - LocalCacheService, LocalStoreService, MessageBus, OnboardingService, diff --git a/src/Squidex/app/framework/services/local-cache.service.spec.ts b/src/Squidex/app/framework/services/local-cache.service.spec.ts deleted file mode 100644 index f2380bf6d..000000000 --- a/src/Squidex/app/framework/services/local-cache.service.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -import { LocalCacheService, LocalCacheServiceFactory } from './../'; - -describe('LocalCache', () => { - it('should instantiate from factory', () => { - const localCacheService = LocalCacheServiceFactory(); - - expect(localCacheService).toBeDefined(); - }); - - it('should instantiate', () => { - const localCacheService = new LocalCacheService(); - - expect(localCacheService).toBeDefined(); - }); - - it('should get and store item in cache', () => { - const localCacheService = new LocalCacheService(); - - const value = {}; - - localCacheService.set('key', value); - - expect(localCacheService.get('key')).toBe(value); - }); - - it('should not retrieve item if cleared', () => { - const localCacheService = new LocalCacheService(); - - const value = {}; - - localCacheService.set('key', value); - localCacheService.clear(true); - - expect(localCacheService.get('key')).toBeUndefined(); - }); - - it('should not retrieve item if removed', () => { - const localCacheService = new LocalCacheService(); - - const value = {}; - - localCacheService.set('key', value); - localCacheService.remove('key'); - - expect(localCacheService.get('key')).toBeUndefined(); - }); - - it('should not retrieve item if expired', () => { - const localCacheService = new LocalCacheService(); - - const value = {}; - - localCacheService.set('key', value); - - expect(localCacheService.get('key', new Date().getTime() + 400)).toBeUndefined(); - }); -}); \ No newline at end of file diff --git a/src/Squidex/app/framework/services/local-cache.service.ts b/src/Squidex/app/framework/services/local-cache.service.ts deleted file mode 100644 index 565115f5a..000000000 --- a/src/Squidex/app/framework/services/local-cache.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -import { Injectable } from '@angular/core'; - -interface Entry { value: any; expires: number; } - -export const LocalCacheServiceFactory = () => { - return new LocalCacheService(); -}; - -@Injectable() -export class LocalCacheService { - private readonly entries: { [key: string]: Entry } = {}; - - public clear(force: boolean) { - const now = new Date().getTime(); - - for (let key in this.entries) { - if (this.entries.hasOwnProperty(key)) { - const entry = this.entries[key]; - - if (force || LocalCacheService.isExpired(now, entry)) { - delete this.entries[key]; - } - } - } - } - - public get(key: string, now?: number): T | undefined { - const entry = this.entries[key]; - - if (entry) { - now = now || new Date().getTime(); - - if (!LocalCacheService.isExpired(now, entry)) { - delete this.entries[key]; - - return entry.value; - } - } - - return undefined; - } - - public set(key: string, value: T, expiresIn = 100) { - this.entries[key] = { value, expires: new Date().getTime() + expiresIn }; - } - - public remove(key: string) { - delete this.entries[key]; - } - - private static isExpired(now: number, entry: Entry): boolean { - return entry.expires < now; - } -} \ No newline at end of file diff --git a/src/Squidex/app/shared/services/assets.service.spec.ts b/src/Squidex/app/shared/services/assets.service.spec.ts index 25a6bc090..0fdb61ed0 100644 --- a/src/Squidex/app/shared/services/assets.service.spec.ts +++ b/src/Squidex/app/shared/services/assets.service.spec.ts @@ -16,7 +16,6 @@ import { AssetReplacedDto, AssetsService, DateTime, - LocalCacheService, UpdateAssetDto, Version, Versioned @@ -70,7 +69,6 @@ describe('AssetsService', () => { ], providers: [ AssetsService, - LocalCacheService, { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, { provide: AnalyticsService, useValue: new AnalyticsService() } ] @@ -216,29 +214,6 @@ describe('AssetsService', () => { new Version('2'))); })); - it('should provide entry from cache if not found', - inject([LocalCacheService, AssetsService, HttpTestingController], (localCache: LocalCacheService, assetsService: AssetsService, httpMock: HttpTestingController) => { - - const cached = {}; - - localCache.set('asset.123', cached, 10000); - - let asset: AssetDto | null = null; - - assetsService.getAsset('my-app', '123').subscribe(result => { - asset = result; - }); - - const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets/123'); - - expect(req.request.method).toEqual('GET'); - expect(req.request.headers.get('If-Match')).toBeNull(); - - req.flush({}, { status: 404, statusText: '404' }); - - expect(asset).toBe(cached); - })); - it('should append query to find by name', inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => { diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index 5bf2850a7..ce2c3071c 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { HttpClient, HttpErrorResponse, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpEventType, HttpHeaders, HttpRequest, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; @@ -13,7 +13,6 @@ import { AnalyticsService, ApiUrlConfig, DateTime, - LocalCacheService, HTTP, Version, Versioned @@ -106,8 +105,7 @@ export class AssetsService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, - private readonly analytics: AnalyticsService, - private readonly localCache: LocalCacheService + private readonly analytics: AnalyticsService ) { } @@ -202,8 +200,6 @@ export class AssetsService { assetUrl, new Version(event.headers.get('etag'))); - this.localCache.set(`asset.${dto.id}`, dto, 5000); - return dto; } }) @@ -239,17 +235,6 @@ export class AssetsService { assetUrl, response.version); }) - .catch(error => { - if (error instanceof HttpErrorResponse && error.status === 404) { - const cached = this.localCache.get(`asset.${id}`); - - if (cached) { - return Observable.of(cached); - } - } - - return Observable.throw(error); - }) .pretifyError('Failed to load assets. Please reload.'); } @@ -298,8 +283,6 @@ export class AssetsService { return HTTP.deleteVersioned(this.http, url, version) .do(() => { this.analytics.trackEvent('Analytics', 'Deleted', appName); - - this.localCache.remove(`asset.${id}`); }) .pretifyError('Failed to delete asset. Please reload.'); } diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index bc0569cd6..e8ba7abdc 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -15,7 +15,6 @@ import { ContentsDto, ContentsService, DateTime, - LocalCacheService, Version } from './../'; @@ -97,7 +96,6 @@ describe('ContentsService', () => { ], providers: [ ContentsService, - LocalCacheService, { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, { provide: AnalyticsService, useValue: new AnalyticsService() } ] @@ -166,11 +164,7 @@ describe('ContentsService', () => { it('should append query to get request as search', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - let contents: ContentsDto | null = null; - - contentsService.getContents('my-app', 'my-schema', 17, 13, 'my-query').subscribe(result => { - contents = result; - }); + contentsService.getContents('my-app', 'my-schema', 17, 13, 'my-query').subscribe(); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema?$search="my-query"&$top=17&$skip=13'); @@ -183,11 +177,7 @@ describe('ContentsService', () => { it('should append ids to get request with ids', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - let contents: ContentsDto | null = null; - - contentsService.getContents('my-app', 'my-schema', 17, 13, undefined, ['id1', 'id2']).subscribe(result => { - contents = result; - }); + contentsService.getContents('my-app', 'my-schema', 17, 13, undefined, ['id1', 'id2']).subscribe(); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema?$top=17&$skip=13&ids=id1,id2'); @@ -200,11 +190,7 @@ describe('ContentsService', () => { it('should append query to get request as plain query string', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { - let contents: ContentsDto | null = null; - - contentsService.getContents('my-app', 'my-schema', 17, 13, '$filter=my-filter').subscribe(result => { - contents = result; - }); + contentsService.getContents('my-app', 'my-schema', 17, 13, '$filter=my-filter').subscribe(); const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema?$filter=my-filter&$top=17&$skip=13'); @@ -250,29 +236,6 @@ describe('ContentsService', () => { new Version('2'))); })); - it('should provide entry from cache if not found', - inject([LocalCacheService, ContentsService, HttpTestingController], (localCache: LocalCacheService, contentsService: ContentsService, httpMock: HttpTestingController) => { - - const cached = {}; - - localCache.set('content.1', cached, 10000); - - let content: ContentDto | null = null; - - contentsService.getContent('my-app', 'my-schema', '1').subscribe(result => { - content = result; - }); - - const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/1'); - - expect(req.request.method).toEqual('GET'); - expect(req.request.headers.get('If-Match')).toBeNull(); - - req.flush({}, { status: 404, statusText: '404' }); - - expect(content).toBe(cached); - })); - it('should make post request to create content', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index d0bedab87..bc7706892 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; @@ -15,7 +15,6 @@ import { AnalyticsService, ApiUrlConfig, DateTime, - LocalCacheService, HTTP, Version, Versioned @@ -96,8 +95,7 @@ export class ContentsService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, - private readonly analytics: AnalyticsService, - private readonly localCache: LocalCacheService + private readonly analytics: AnalyticsService ) { } @@ -172,17 +170,6 @@ export class ContentsService { body.data, response.version); }) - .catch(error => { - if (error instanceof HttpErrorResponse && error.status === 404) { - const cached = this.localCache.get(`content.${id}`); - - if (cached) { - return Observable.of(cached); - } - } - - return Observable.throw(error); - }) .pretifyError('Failed to load content. Please reload.'); } @@ -215,8 +202,6 @@ export class ContentsService { }) .do(content => { this.analytics.trackEvent('Content', 'Created', appName); - - this.localCache.set(`content.${content.id}`, content, 5000); }) .pretifyError('Failed to create content. Please reload.'); } @@ -232,8 +217,6 @@ export class ContentsService { }) .do(() => { this.analytics.trackEvent('Content', 'Updated', appName); - - this.localCache.set(`content.${id}`, dto, 5000); }) .pretifyError('Failed to update content. Please reload.'); } @@ -244,8 +227,6 @@ export class ContentsService { return HTTP.deleteVersioned(this.http, url, version) .do(() => { this.analytics.trackEvent('Content', 'Deleted', appName); - - this.localCache.remove(`content.${id}`); }) .pretifyError('Failed to delete content. Please reload.'); } diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 2f8ec096e..35cd45ae8 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -16,7 +16,6 @@ import { createProperties, DateTime, FieldDto, - LocalCacheService, SchemaDetailsDto, SchemaDto, SchemaPropertiesDto, @@ -197,7 +196,6 @@ describe('SchemasService', () => { HttpClientTestingModule ], providers: [ - LocalCacheService, SchemasService, { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, { provide: AnalyticsService, useValue: new AnalyticsService() } @@ -433,29 +431,6 @@ describe('SchemasService', () => { '')); })); - it('should provide entry from cache if not found', - inject([LocalCacheService, SchemasService, HttpTestingController], (localCache: LocalCacheService, schemasService: SchemasService, httpMock: HttpTestingController) => { - - const cached = {}; - - localCache.set('schema.my-app.my-schema', cached, 10000); - - let schema: SchemaDetailsDto | null = null; - - schemasService.getSchema('my-app', 'my-schema').subscribe(result => { - schema = result; - }); - - const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema'); - - expect(req.request.method).toEqual('GET'); - expect(req.request.headers.get('If-Match')).toBeNull(); - - req.flush({}, { status: 404, statusText: '404' }); - - expect(schema).toBe(cached); - })); - it('should make post request to create schema', inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index c5904552d..440b11b18 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ValidatorFn, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; @@ -16,7 +16,6 @@ import { AnalyticsService, ApiUrlConfig, DateTime, - LocalCacheService, HTTP, ValidatorsEx, Version, @@ -786,8 +785,7 @@ export class SchemasService { constructor( private readonly http: HttpClient, private readonly apiUrl: ApiUrlConfig, - private readonly analytics: AnalyticsService, - private readonly localCache: LocalCacheService + private readonly analytics: AnalyticsService ) { } @@ -858,17 +856,6 @@ export class SchemasService { body.scriptDelete, body.scriptChange); }) - .catch(error => { - if (error instanceof HttpErrorResponse && error.status === 404) { - const cached = this.localCache.get(`schema.${appName}.${id}`); - - if (cached) { - return Observable.of(cached); - } - } - - return Observable.throw(error); - }) .pretifyError('Failed to load schema. Please reload.'); } @@ -900,9 +887,6 @@ export class SchemasService { }) .do(schema => { this.analytics.trackEvent('Schema', 'Created', appName); - - this.localCache.set(`schema.${appName}.${schema.id}`, schema, 5000); - this.localCache.set(`schema.${appName}.${schema.name}`, schema, 5000); }) .pretifyError('Failed to create schema. Please reload.'); } @@ -935,9 +919,6 @@ export class SchemasService { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}`); return HTTP.deleteVersioned(this.http, url, version) - .do(() => { - this.localCache.remove(`schema.${appName}.${schemaName}`); - }) .do(() => { this.analytics.trackEvent('Schema', 'Deleted', appName); }) diff --git a/src/Squidex/package.json b/src/Squidex/package.json index 8ea7b3e2b..7e6427fb0 100644 --- a/src/Squidex/package.json +++ b/src/Squidex/package.json @@ -15,36 +15,36 @@ "build:clean": "rimraf wwwroot/build" }, "dependencies": { - "@angular/animations": "5.0.5", - "@angular/common": "5.0.5", - "@angular/compiler": "5.0.5", - "@angular/core": "5.0.5", - "@angular/forms": "5.0.5", - "@angular/http": "5.0.5", - "@angular/platform-browser": "5.0.5", - "@angular/platform-browser-dynamic": "5.0.5", - "@angular/platform-server": "5.0.5", - "@angular/router": "5.0.5", + "@angular/animations": "5.1.0", + "@angular/common": "5.1.0", + "@angular/compiler": "5.1.0", + "@angular/core": "5.1.0", + "@angular/forms": "5.1.0", + "@angular/http": "5.1.0", + "@angular/platform-browser": "5.1.0", + "@angular/platform-browser-dynamic": "5.1.0", + "@angular/platform-server": "5.1.0", + "@angular/router": "5.1.0", "angular2-chartjs": "0.4.1", "babel-polyfill": "6.26.0", "bootstrap": "4.0.0-alpha.6", - "core-js": "2.5.1", + "core-js": "2.5.3", "graphiql": "0.11.10", - "moment": "2.19.3", + "moment": "2.19.4", "mousetrap": "1.6.1", "ng2-dnd": "5.0.2", "oidc-client": "1.4.1", - "pikaday": "1.6.1", + "pikaday": "1.7.0", "progressbar.js": "1.0.1", "react": "16.2.0", "react-dom": "16.2.0", - "rxjs": "5.5.2", + "rxjs": "5.5.5", "zone.js": "0.8.18" }, "devDependencies": { - "@angular/compiler": "5.0.5", - "@angular/compiler-cli": "5.0.5", - "@ngtools/webpack": "1.8.5", + "@angular/compiler": "5.1.0", + "@angular/compiler-cli": "5.1.0", + "@ngtools/webpack": "1.9.0", "@types/core-js": "0.9.35", "@types/jasmine": "2.5.45", "@types/mousetrap": "1.5.34", @@ -54,7 +54,7 @@ "angular2-router-loader": "0.3.5", "angular2-template-loader": "0.6.2", "awesome-typescript-loader": "3.4.1", - "codelyzer": "4.0.1", + "codelyzer": "4.0.2", "cpx": "1.5.0", "css-loader": "0.28.7", "exports-loader": "0.6.4", @@ -88,10 +88,10 @@ "tslint": "5.8.0", "tslint-loader": "3.5.3", "typemoq": "2.1.0", - "typescript": "2.4.2", + "typescript": "2.5.3", "underscore": "1.8.3", - "webpack": "3.9.1", - "webpack-dev-server": "2.9.5", + "webpack": "3.10.0", + "webpack-dev-server": "2.9.7", "webpack-merge": "4.1.1" } } diff --git a/tests/Benchmarks/Benchmark.cs b/tests/Benchmarks/Benchmark.cs deleted file mode 100644 index 2bf549ceb..000000000 --- a/tests/Benchmarks/Benchmark.cs +++ /dev/null @@ -1,31 +0,0 @@ -// ========================================================================== -// IBenchmark.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -namespace Benchmarks -{ - public abstract class Benchmark - { - public virtual void Initialize() - { - } - - public virtual void RunInitialize() - { - } - - public virtual void RunCleanup() - { - } - - public virtual void Cleanup() - { - } - - public abstract long Run(); - } -} diff --git a/tests/Benchmarks/Benchmarks.csproj b/tests/Benchmarks/Benchmarks.csproj deleted file mode 100644 index e8ecbf1d7..000000000 --- a/tests/Benchmarks/Benchmarks.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - Exe - netcoreapp2.0 - - - - - - - - - - - - - - - - C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection\2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll - - - - ..\..\Squidex.ruleset - - diff --git a/tests/Benchmarks/Program.cs b/tests/Benchmarks/Program.cs deleted file mode 100644 index c04991e0f..000000000 --- a/tests/Benchmarks/Program.cs +++ /dev/null @@ -1,96 +0,0 @@ -// ========================================================================== -// Program.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Benchmarks.Tests; - -namespace Benchmarks -{ - public static class Program - { - private static readonly List<(string Name, Benchmark Benchmark)> Benchmarks = new Benchmark[] - { - new AppendToEventStore(), - new AppendToEventStoreWithManyWriters(), - new HandleEvents(), - new HandleEventsWithManyWriters(), - new ReadSchemaState() - }.Select(x => (x.GetType().Name, x)).ToList(); - - public static void Main(string[] args) - { - var name = "ReadSchemaState"; - - var selected = Benchmarks.Find(x => x.Name == name); - - if (selected.Benchmark == null) - { - Console.WriteLine($"'{name}' is not a valid benchmark, please try: "); - - foreach (var b in Benchmarks) - { - Console.WriteLine($" * {b.Name}"); - } - } - else - { - const int numRuns = 3; - - try - { - var elapsed = 0d; - var count = 0L; - - Console.WriteLine($"{selected.Name}: Initialized"); - - selected.Benchmark.Initialize(); - - for (var run = 0; run < numRuns; run++) - { - try - { - selected.Benchmark.RunInitialize(); - - var watch = Stopwatch.StartNew(); - - count += selected.Benchmark.Run(); - - watch.Stop(); - - elapsed += watch.ElapsedMilliseconds; - - Console.WriteLine($"{selected.Name}: Run {run + 1} finished"); - } - finally - { - selected.Benchmark.RunCleanup(); - } - } - - var averageElapsed = TimeSpan.FromMilliseconds(elapsed / numRuns); - var averageSeconds = Math.Round(count / (numRuns * averageElapsed.TotalSeconds), 2); - - Console.WriteLine($"{selected.Name}: Completed after {averageElapsed}, {averageSeconds} items/s"); - } - catch (Exception e) - { - Console.WriteLine($"Benchmark failed with '{e.Message}'"); - } - finally - { - selected.Benchmark.Cleanup(); - } - } - - Console.ReadLine(); - } - } -} diff --git a/tests/Benchmarks/Properties/launchSettings.json b/tests/Benchmarks/Properties/launchSettings.json deleted file mode 100644 index 05fb17fa5..000000000 --- a/tests/Benchmarks/Properties/launchSettings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "profiles": { - "Benchmarks": { - "commandName": "Project" - } - } -} \ No newline at end of file diff --git a/tests/Benchmarks/Services.cs b/tests/Benchmarks/Services.cs deleted file mode 100644 index 1c7a770d0..000000000 --- a/tests/Benchmarks/Services.cs +++ /dev/null @@ -1,145 +0,0 @@ -// ========================================================================== -// Services.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Benchmarks.Tests.TestData; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using MongoDB.Driver; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using NodaTime; -using NodaTime.Serialization.JsonNet; -using Squidex.Domain.Apps.Core.Apps.Json; -using Squidex.Domain.Apps.Core.Rules.Json; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Core.Schemas.Json; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.MongoDb; -using Squidex.Infrastructure.States; - -namespace Benchmarks -{ - public static class Services - { - public static IServiceProvider Create() - { - var services = new ServiceCollection(); - - services.AddSingleton(CreateTypeNameRegistry()); - - services.AddSingleton(); - - services.AddTransient(); - - services.AddSingleton( - new MongoClient("mongodb://localhost")); - - services.AddSingleton( - new SemanticLog(new ILogChannel[0], new ILogAppender[0], () => new JsonLogWriter())); - - services.AddSingleton( - new MemoryCache(Options.Create(new MemoryCacheOptions()))); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(); - - services.AddSingleton(c => - JsonSerializer.Create(c.GetRequiredService())); - - services.AddSingleton(c => - CreateJsonSerializerSettings(c.GetRequiredService(), c.GetRequiredService())); - - services.AddSingleton(c => - c.GetRequiredService().GetDatabase(Guid.NewGuid().ToString())); - - return services.BuildServiceProvider(); - } - - public static void Cleanup(this IServiceProvider services) - { - var mongoClient = services.GetRequiredService(); - var mongoDatabase = services.GetRequiredService(); - - mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName); - - if (services is IDisposable disposable) - { - disposable.Dispose(); - } - } - - private static TypeNameRegistry CreateTypeNameRegistry() - { - var result = new TypeNameRegistry(); - - result.Map(typeof(MyEvent)); - - return result; - } - - private static JsonSerializerSettings CreateJsonSerializerSettings(TypeNameRegistry typeNameRegistry, FieldRegistry fieldRegistry) - { - var settings = new JsonSerializerSettings(); - - settings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry); - - settings.ContractResolver = new ConverterContractResolver( - new AppClientsConverter(), - new AppContributorsConverter(), - new ClaimsPrincipalConverter(), - new InstantConverter(), - new LanguageConverter(), - new LanguagesConfigConverter(), - new NamedGuidIdConverter(), - new NamedLongIdConverter(), - new NamedStringIdConverter(), - new PropertiesBagConverter(), - new PropertiesBagConverter(), - new RefTokenConverter(), - new RuleConverter(), - new SchemaConverter(fieldRegistry), - new StringEnumConverter()); - - settings.NullValueHandling = NullValueHandling.Ignore; - - settings.DateFormatHandling = DateFormatHandling.IsoDateFormat; - settings.DateParseHandling = DateParseHandling.None; - - settings.TypeNameHandling = TypeNameHandling.Auto; - - settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); - - BsonJsonConvention.Register(JsonSerializer.Create(settings)); - - return settings; - } - } -} diff --git a/tests/Benchmarks/Tests/AppendToEventStore.cs b/tests/Benchmarks/Tests/AppendToEventStore.cs deleted file mode 100644 index 679330ade..000000000 --- a/tests/Benchmarks/Tests/AppendToEventStore.cs +++ /dev/null @@ -1,53 +0,0 @@ -// ========================================================================== -// AppendToEventStore.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Benchmarks.Utils; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure.EventSourcing; - -namespace Benchmarks.Tests -{ - public sealed class AppendToEventStore : Benchmark - { - private IServiceProvider services; - private IEventStore eventStore; - - public override void RunInitialize() - { - services = Services.Create(); - - eventStore = services.GetRequiredService(); - } - - public override long Run() - { - const long numCommits = 100; - const long numStreams = 20; - - for (var streamId = 0; streamId < numStreams; streamId++) - { - var eventOffset = -1; - var streamName = streamId.ToString(); - - for (var commitId = 0; commitId < numCommits; commitId++) - { - eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset, new[] { Helper.CreateEventData() }).Wait(); - eventOffset++; - } - } - - return numCommits * numStreams; - } - - public override void RunCleanup() - { - services.Cleanup(); - } - } -} diff --git a/tests/Benchmarks/Tests/AppendToEventStoreWithManyWriters.cs b/tests/Benchmarks/Tests/AppendToEventStoreWithManyWriters.cs deleted file mode 100644 index 93ca46661..000000000 --- a/tests/Benchmarks/Tests/AppendToEventStoreWithManyWriters.cs +++ /dev/null @@ -1,52 +0,0 @@ -// ========================================================================== -// AppendToEventStoreWithManyWriters.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Benchmarks.Utils; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure.EventSourcing; - -namespace Benchmarks.Tests -{ - public sealed class AppendToEventStoreWithManyWriters : Benchmark - { - private IServiceProvider services; - private IEventStore eventStore; - - public override void RunInitialize() - { - services = Services.Create(); - - eventStore = services.GetRequiredService(); - } - - public override long Run() - { - const long numCommits = 200; - const long numStreams = 100; - - Parallel.For(0, numStreams, streamId => - { - var streamName = streamId.ToString(); - - for (var commitId = 0; commitId < numCommits; commitId++) - { - eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, new[] { Helper.CreateEventData() }).Wait(); - } - }); - - return numCommits * numStreams; - } - - public override void RunCleanup() - { - services.Cleanup(); - } - } -} diff --git a/tests/Benchmarks/Tests/HandleEvents.cs b/tests/Benchmarks/Tests/HandleEvents.cs deleted file mode 100644 index d49c37b7b..000000000 --- a/tests/Benchmarks/Tests/HandleEvents.cs +++ /dev/null @@ -1,63 +0,0 @@ -// ========================================================================== -// HandleEvents.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Benchmarks.Tests.TestData; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.EventSourcing.Grains; -using Squidex.Infrastructure.States; - -namespace Benchmarks.Tests -{ - public sealed class HandleEvents : Benchmark - { - private const int NumEvents = 5000; - private IServiceProvider services; - private IEventStore eventStore; - private IEventDataFormatter eventDataFormatter; - private EventConsumerGrain eventConsumerGrain; - private MyEventConsumer eventConsumer; - - public override void RunInitialize() - { - services = Services.Create(); - - eventConsumer = new MyEventConsumer(NumEvents); - - eventStore = services.GetRequiredService(); - - eventDataFormatter = services.GetRequiredService(); - eventConsumerGrain = services.GetRequiredService(); - - eventConsumerGrain.ActivateAsync("Test", services.GetRequiredService()).Wait(); - eventConsumerGrain.Activate(eventConsumer); - } - - public override long Run() - { - var streamName = Guid.NewGuid().ToString(); - - for (var eventId = 0; eventId < NumEvents; eventId++) - { - var eventData = eventDataFormatter.ToEventData(new Envelope(new MyEvent { EventNumber = eventId + 1 }), Guid.NewGuid()); - - eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventId - 1, new[] { eventData }).Wait(); - } - - eventConsumer.WaitAndVerify(); - - return NumEvents; - } - - public override void RunCleanup() - { - services.Cleanup(); - } - } -} diff --git a/tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs b/tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs deleted file mode 100644 index c98899c91..000000000 --- a/tests/Benchmarks/Tests/HandleEventsWithManyWriters.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// HandleEventsWithManyWriters.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using Benchmarks.Tests.TestData; -using Microsoft.Extensions.DependencyInjection; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.EventSourcing.Grains; -using Squidex.Infrastructure.States; - -namespace Benchmarks.Tests -{ - public sealed class HandleEventsWithManyWriters : Benchmark - { - private const int NumCommits = 200; - private const int NumStreams = 10; - private IServiceProvider services; - private IEventStore eventStore; - private IEventDataFormatter eventDataFormatter; - private EventConsumerGrain eventConsumerGrain; - private MyEventConsumer eventConsumer; - - public override void RunInitialize() - { - services = Services.Create(); - - eventConsumer = new MyEventConsumer(NumStreams * NumCommits); - - eventStore = services.GetRequiredService(); - eventDataFormatter = services.GetRequiredService(); - - eventConsumerGrain = services.GetRequiredService(); - - eventConsumerGrain.ActivateAsync("Test", services.GetRequiredService()).Wait(); - eventConsumerGrain.Activate(eventConsumer); - } - - public override long Run() - { - Parallel.For(0, NumStreams, streamId => - { - var eventOffset = -1; - var streamName = streamId.ToString(); - - for (var commitId = 0; commitId < NumCommits; commitId++) - { - var eventData = eventDataFormatter.ToEventData(new Envelope(new MyEvent()), Guid.NewGuid()); - - eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset - 1, new[] { eventData }).Wait(); - eventOffset++; - } - }); - - eventConsumer.WaitAndVerify(); - - return NumStreams * NumCommits; - } - - public override void RunCleanup() - { - services.Cleanup(); - } - } -} diff --git a/tests/Benchmarks/Tests/ReadSchemaState.cs b/tests/Benchmarks/Tests/ReadSchemaState.cs deleted file mode 100644 index f02f50ae4..000000000 --- a/tests/Benchmarks/Tests/ReadSchemaState.cs +++ /dev/null @@ -1,102 +0,0 @@ -// ========================================================================== -// ReadSchemaState.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Immutable; -using Benchmarks.Tests.TestData; -using Microsoft.Extensions.DependencyInjection; -using NodaTime; -using Squidex.Domain.Apps.Core; -using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Core.Rules.Actions; -using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.State.Grains; -using Squidex.Infrastructure; -using Squidex.Infrastructure.States; - -namespace Benchmarks.Tests -{ - public class ReadSchemaState : Benchmark - { - private IServiceProvider services; - private MyAppState grain; - - public override void Initialize() - { - services = Services.Create(); - - grain = services.GetRequiredService().GetSynchronizedAsync("DEFAULT").Result; - - var state = new AppStateGrainState - { - App = new JsonAppEntity - { - Id = Guid.NewGuid() - } - }; - - state.Schemas = ImmutableDictionary.Empty; - - for (var i = 1; i <= 100; i++) - { - var schema = new JsonSchemaEntity - { - Id = Guid.NewGuid(), - Created = SystemClock.Instance.GetCurrentInstant(), - CreatedBy = new RefToken("user", "1"), - LastModified = SystemClock.Instance.GetCurrentInstant(), - LastModifiedBy = new RefToken("user", "1"), - SchemaDef = new Schema("Name") - }; - - for (var j = 1; j < 30; j++) - { - schema.SchemaDef = schema.SchemaDef.AddField(new StringField(j, j.ToString(), Partitioning.Invariant)); - } - - state.Schemas = state.Schemas.Add(schema.Id, schema); - } - - state.Rules = ImmutableDictionary.Empty; - - for (var i = 0; i < 100; i++) - { - var rule = new JsonRuleEntity - { - Id = Guid.NewGuid(), - Created = SystemClock.Instance.GetCurrentInstant(), - CreatedBy = new RefToken("user", "1"), - LastModified = SystemClock.Instance.GetCurrentInstant(), - LastModifiedBy = new RefToken("user", "1"), - RuleDef = new Rule(new ContentChangedTrigger(), new WebhookAction()) - }; - - state.Rules = state.Rules.Add(rule.Id, rule); - } - - grain.SetState(state); - grain.WriteStateAsync().Wait(); - } - - public override long Run() - { - for (var i = 0; i < 10; i++) - { - grain.ReadStateAsync().Wait(); - } - - return 10; - } - - public override void Cleanup() - { - services.Cleanup(); - } - } -} diff --git a/tests/Benchmarks/Tests/TestData/MyAppState.cs b/tests/Benchmarks/Tests/TestData/MyAppState.cs deleted file mode 100644 index 1340b9f7d..000000000 --- a/tests/Benchmarks/Tests/TestData/MyAppState.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ========================================================================== -// MyAppState.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.State.Grains; -using Squidex.Infrastructure.States; - -namespace Benchmarks.Tests.TestData -{ - public sealed class MyAppState : IStatefulObject - { - private IPersistence persistence; - private AppStateGrainState state; - - public Task ActivateAsync(string key, IStore store) - { - persistence = store.WithSnapshots(key, s => state = s); - - return persistence.ReadAsync(); - } - - public void SetState(AppStateGrainState state) - { - this.state = state; - } - - public Task WriteStateAsync() - { - return persistence.WriteSnapshotAsync(state); - } - - public Task ReadStateAsync() - { - return persistence.ReadAsync(); - } - } -} diff --git a/tests/Benchmarks/Tests/TestData/MyEvent.cs b/tests/Benchmarks/Tests/TestData/MyEvent.cs deleted file mode 100644 index a11bd99bd..000000000 --- a/tests/Benchmarks/Tests/TestData/MyEvent.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// MyEvent.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; - -namespace Benchmarks.Tests.TestData -{ - [TypeName("MyEvent")] - public sealed class MyEvent : IEvent - { - public int EventNumber { get; set; } - } -} \ No newline at end of file diff --git a/tests/Benchmarks/Tests/TestData/MyEventConsumer.cs b/tests/Benchmarks/Tests/TestData/MyEventConsumer.cs deleted file mode 100644 index 153910c55..000000000 --- a/tests/Benchmarks/Tests/TestData/MyEventConsumer.cs +++ /dev/null @@ -1,79 +0,0 @@ -// ========================================================================== -// MyEventConsumer.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.Tasks; - -namespace Benchmarks.Tests.TestData -{ - public sealed class MyEventConsumer : IEventConsumer - { - private readonly TaskCompletionSource completion = new TaskCompletionSource(); - private readonly int numEvents; - - public List EventNumbers { get; } = new List(); - - public string Name - { - get { return typeof(MyEventConsumer).Name; } - } - - public string EventsFilter - { - get { return string.Empty; } - } - - public MyEventConsumer(int numEvents) - { - this.numEvents = numEvents; - } - - public Task ClearAsync() - { - return TaskHelper.Done; - } - - public void WaitAndVerify() - { - completion.Task.Wait(); - - if (EventNumbers.Count != numEvents) - { - throw new InvalidOperationException($"{EventNumbers.Count} Events have been handled"); - } - - for (var i = 0; i < EventNumbers.Count; i++) - { - var value = EventNumbers[i]; - - if (value != i + 1) - { - throw new InvalidOperationException($"Event[{i}] != value"); - } - } - } - - public Task On(Envelope @event) - { - if (@event.Payload is MyEvent myEvent) - { - EventNumbers.Add(myEvent.EventNumber); - - if (myEvent.EventNumber == numEvents) - { - completion.SetResult(true); - } - } - - return TaskHelper.Done; - } - } -} \ No newline at end of file diff --git a/tests/Benchmarks/Utils/Helper.cs b/tests/Benchmarks/Utils/Helper.cs deleted file mode 100644 index 68da44773..000000000 --- a/tests/Benchmarks/Utils/Helper.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ========================================================================== -// Helper.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Infrastructure.EventSourcing; - -namespace Benchmarks.Utils -{ - public static class Helper - { - public static EventData CreateEventData() - { - return new EventData { EventId = Guid.NewGuid(), Metadata = "EventMetdata", Payload = "EventPayload", Type = "MyEvent" }; - } - } -} diff --git a/tests/RunCoverage.ps1 b/tests/RunCoverage.ps1 index 594623942..ae66df3c9 100644 --- a/tests/RunCoverage.ps1 +++ b/tests/RunCoverage.ps1 @@ -1,8 +1,7 @@ Param( [switch]$infrastructure, [switch]$appsCore, - [switch]$appsRead, - [switch]$appsWrite, + [switch]$appsEntities, [switch]$users, [switch]$all ) @@ -43,25 +42,14 @@ if ($all -Or $appsCore) { -oldStyle } -if ($all -Or $appsRead) { +if ($all -Or $appsEntities) { &"$folderHome\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ` -register:user ` -target:"C:\Program Files\dotnet\dotnet.exe" ` - -targetargs:"test $folderWorking\Squidex.Domain.Apps.Read.Tests\Squidex.Domain.Apps.Read.Tests.csproj" ` - -filter:"+[Squidex.Domain.Apps.Read*]*" ` + -targetargs:"test $folderWorking\Squidex.Domain.Apps.Entities.Tests\Squidex.Domain.Apps.Entities.Tests.csproj" ` + -filter:"+[Squidex.Domain.Apps.Entities*]*" ` -skipautoprops ` - -output:"$folderWorking\$folderReports\Read.xml" ` - -oldStyle -} - -if ($all -Or $appsWrite) { - &"$folderHome\.nuget\packages\OpenCover\4.6.519\tools\OpenCover.Console.exe" ` - -register:user ` - -target:"C:\Program Files\dotnet\dotnet.exe" ` - -targetargs:"test $folderWorking\Squidex.Domain.Apps.Write.Tests\Squidex.Domain.Apps.Write.Tests.csproj" ` - -filter:"+[Squidex.Domain.Apps.Write*]*" ` - -skipautoprops ` - -output:"$folderWorking\$folderReports\Write.xml" ` + -output:"$folderWorking\$folderReports\Entities.xml" ` -oldStyle } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs index f885789a3..0eeb4b842 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs @@ -86,12 +86,14 @@ namespace Squidex.Domain.Apps.Core.Model.Contents .AddValue("iv", 1)) .AddField("field2", new ContentFieldData() - .AddValue("de", 2)); + .AddValue("de", 2) + .AddValue("it", 2)); var rhs = new NamedContentData() .AddField("field2", new ContentFieldData() + .AddValue("it", 3) .AddValue("en", 3)) .AddField("field3", new ContentFieldData() @@ -104,6 +106,7 @@ namespace Squidex.Domain.Apps.Core.Model.Contents .AddValue("iv", 1)) .AddField("field2", new ContentFieldData() + .AddValue("it", 2) .AddValue("de", 2) .AddValue("en", 3)) .AddField("field3", @@ -127,12 +130,14 @@ namespace Squidex.Domain.Apps.Core.Model.Contents .AddValue("iv", 1)) .AddField(2, new ContentFieldData() - .AddValue("de", 2)); + .AddValue("de", 2) + .AddValue("it", 2)); var rhs = new IdContentData() .AddField(2, new ContentFieldData() + .AddValue("it", 3) .AddValue("en", 3)) .AddField(3, new ContentFieldData() @@ -145,6 +150,7 @@ namespace Squidex.Domain.Apps.Core.Model.Contents .AddValue("iv", 1)) .AddField(2, new ContentFieldData() + .AddValue("it", 2) .AddValue("de", 2) .AddValue("en", 3)) .AddField(3, diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj index 7b62d9673..e1188014a 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs similarity index 89% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs index 0f2cc3ccb..bb778526d 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppCommandMiddlewareTests.cs @@ -9,18 +9,16 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; -using Squidex.Domain.Apps.Write.Apps.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Shared.Users; using Xunit; -namespace Squidex.Domain.Apps.Write.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class AppCommandMiddlewareTests : HandlerTestBase { @@ -28,16 +26,19 @@ namespace Squidex.Domain.Apps.Write.Apps private readonly IAppPlansProvider appPlansProvider = A.Fake(); private readonly IAppPlanBillingManager appPlansBillingManager = A.Fake(); private readonly IUserResolver userResolver = A.Fake(); - private readonly AppCommandMiddleware sut; - private readonly AppDomainObject app; + private readonly AppDomainObject app = new AppDomainObject(); private readonly Language language = Language.DE; private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientName = "client"; + private readonly AppCommandMiddleware sut; - public AppCommandMiddlewareTests() + protected override Guid Id { - app = new AppDomainObject(AppId, -1); + get { return AppId; } + } + public AppCommandMiddlewareTests() + { A.CallTo(() => appProvider.GetAppAsync(AppName)) .Returns((IAppEntity)null); @@ -61,7 +62,7 @@ namespace Squidex.Domain.Apps.Write.Apps } [Fact] - public async Task AssignContributor_should_assign_if_user_found() + public async Task AssignContributor_should_update_domain_object_if_user_found() { A.CallTo(() => appPlansProvider.GetPlan(null)) .Returns(new ConfigAppLimitsPlan { MaxContributors = -1 }); @@ -146,7 +147,8 @@ namespace Squidex.Domain.Apps.Write.Apps await sut.HandleAsync(context); }); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan")).MustHaveHappened(); + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, "my-plan")) + .MustHaveHappened(); } [Fact] @@ -155,7 +157,7 @@ namespace Squidex.Domain.Apps.Write.Apps A.CallTo(() => appPlansProvider.IsConfiguredPlan("my-plan")) .Returns(true); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan")) + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, "my-plan")) .Returns(CreateRedirectResult()); CreateApp(); @@ -167,7 +169,7 @@ namespace Squidex.Domain.Apps.Write.Apps await sut.HandleAsync(context); }); - Assert.Null(app.Plan); + Assert.Null(app.State.Plan); } [Fact] @@ -185,7 +187,8 @@ namespace Squidex.Domain.Apps.Write.Apps await sut.HandleAsync(context); }); - A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, app.Id, app.Name, "my-plan")).MustNotHaveHappened(); + A.CallTo(() => appPlansBillingManager.ChangePlanAsync(User.Identifier, AppId, AppName, "my-plan")) + .MustNotHaveHappened(); } [Fact] @@ -231,7 +234,7 @@ namespace Squidex.Domain.Apps.Write.Apps private AppDomainObject CreateApp() { - app.Create(CreateCommand(new CreateApp { Name = AppName })); + app.Create(CreateCommand(new CreateApp { AppId = AppId, Name = AppName })); return app; } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs similarity index 88% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs index c41ae0f9a..bea62e007 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppDomainObjectTests.cs @@ -10,26 +10,25 @@ using System; using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class AppDomainObjectTests : HandlerTestBase { - private readonly AppDomainObject sut; private readonly string contributorId = Guid.NewGuid().ToString(); private readonly string clientId = "client"; private readonly string clientNewName = "My Client"; private readonly string planId = "premium"; + private readonly AppDomainObject sut = new AppDomainObject(); - public AppDomainObjectTests() + protected override Guid Id { - sut = new AppDomainObject(AppId, 0); + get { return AppId; } } [Fact] @@ -48,7 +47,7 @@ namespace Squidex.Domain.Apps.Write.Apps { sut.Create(CreateCommand(new CreateApp { Name = AppName, Actor = User, AppId = AppId })); - Assert.Equal(AppName, sut.Name); + Assert.Equal(AppName, sut.State.Name); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -74,6 +73,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.ChangePlan(CreateCommand(new ChangePlan { PlanId = planId })); + Assert.Equal(planId, sut.State.Plan.PlanId); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppPlanChanged { PlanId = planId }) @@ -96,6 +97,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); + Assert.Equal(AppContributorPermission.Editor, sut.State.Contributors[contributorId]); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppContributorAssigned { ContributorId = contributorId, Permission = AppContributorPermission.Editor }) @@ -119,6 +122,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.AssignContributor(CreateCommand(new AssignContributor { ContributorId = contributorId, Permission = AppContributorPermission.Editor })); sut.RemoveContributor(CreateCommand(new RemoveContributor { ContributorId = contributorId })); + Assert.False(sut.State.Contributors.ContainsKey(contributorId)); + sut.GetUncomittedEvents().Skip(1) .ShouldHaveSameEvents( CreateEvent(new AppContributorRemoved { ContributorId = contributorId }) @@ -143,6 +148,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.AttachClient(CreateCommand(command)); + Assert.True(sut.State.Clients.ContainsKey(clientId)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppClientAttached { Id = clientId, Secret = command.Secret }) @@ -166,6 +173,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.RevokeClient(CreateCommand(new RevokeClient { Id = clientId })); + Assert.False(sut.State.Clients.ContainsKey(clientId)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppClientRevoked { Id = clientId }) @@ -189,6 +198,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.UpdateClient(CreateCommand(new UpdateClient { Id = clientId, Name = clientNewName, Permission = AppClientPermission.Developer })); + Assert.Equal(clientNewName, sut.State.Clients[clientId].Name); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppClientRenamed { Id = clientId, Name = clientNewName }), @@ -212,6 +223,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.AddLanguage(CreateCommand(new AddLanguage { Language = Language.DE })); + Assert.True(sut.State.LanguagesConfig.Contains(Language.DE)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppLanguageAdded { Language = Language.DE }) @@ -235,6 +248,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.RemoveLanguage(CreateCommand(new RemoveLanguage { Language = Language.DE })); + Assert.False(sut.State.LanguagesConfig.Contains(Language.DE)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppLanguageRemoved { Language = Language.DE }) @@ -258,6 +273,8 @@ namespace Squidex.Domain.Apps.Write.Apps sut.UpdateLanguage(CreateCommand(new UpdateLanguage { Language = Language.DE, Fallback = new List { Language.EN } })); + Assert.True(sut.State.LanguagesConfig.Contains(Language.DE)); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateEvent(new AppLanguageUpdated { Language = Language.DE, Fallback = new List { Language.EN } }) @@ -267,22 +284,19 @@ namespace Squidex.Domain.Apps.Write.Apps private void CreateApp() { sut.Create(CreateCommand(new CreateApp { Name = AppName })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void CreateClient() { sut.AttachClient(CreateCommand(new AttachClient { Id = clientId })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void CreateLanguage(Language language) { sut.AddLanguage(CreateCommand(new AddLanguage { Language = language })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } } } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Apps/ConfigAppLimitsProviderTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/ConfigAppLimitsProviderTests.cs similarity index 90% rename from tests/Squidex.Domain.Apps.Read.Tests/Apps/ConfigAppLimitsProviderTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/ConfigAppLimitsProviderTests.cs index f81da552b..f6a6066fb 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Apps/ConfigAppLimitsProviderTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/ConfigAppLimitsProviderTests.cs @@ -9,10 +9,12 @@ using System.Linq; using FakeItEasy; using FluentAssertions; -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; +using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class ConfigAppLimitsProviderTests { @@ -142,7 +144,14 @@ namespace Squidex.Domain.Apps.Read.Apps { var app = A.Dummy(); - A.CallTo(() => app.PlanId).Returns(plan); + if (plan != null) + { + A.CallTo(() => app.Plan).Returns(new AppPlan(new RefToken("user", "me"), plan)); + } + else + { + A.CallTo(() => app.Plan).Returns(null); + } return app; } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs index bae80d9dc..5db4d3687 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppClientsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs @@ -7,13 +7,13 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public class GuardAppClientsTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs index 7942dcd96..6bdfb21c7 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppContributorsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs @@ -9,15 +9,15 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; using Squidex.Shared.Users; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public class GuardAppContributorsTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs index ad4afb684..a3ae0baaf 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppLanguagesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs @@ -8,13 +8,13 @@ using System.Collections.Generic; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public class GuardAppLanguagesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs similarity index 94% rename from tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs index 891c014ef..d653a3635 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/Guards/GuardAppTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs @@ -9,15 +9,13 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Apps.Services; -using Squidex.Domain.Apps.Write.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; using Squidex.Shared.Users; using Xunit; -namespace Squidex.Domain.Apps.Write.Apps.Guards +namespace Squidex.Domain.Apps.Entities.Apps.Guards { public class GuardAppTests { diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Apps/NoopAppPlanBillingManagerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/NoopAppPlanBillingManagerTests.cs similarity index 90% rename from tests/Squidex.Domain.Apps.Read.Tests/Apps/NoopAppPlanBillingManagerTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Apps/NoopAppPlanBillingManagerTests.cs index 17dd3d413..76162f128 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Apps/NoopAppPlanBillingManagerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/NoopAppPlanBillingManagerTests.cs @@ -8,10 +8,10 @@ using System; using System.Threading.Tasks; -using Squidex.Domain.Apps.Read.Apps.Services.Implementations; +using Squidex.Domain.Apps.Entities.Apps.Services.Implementations; using Xunit; -namespace Squidex.Domain.Apps.Read.Apps +namespace Squidex.Domain.Apps.Entities.Apps { public class NoopAppPlanBillingManagerTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs similarity index 80% rename from tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetCommandMiddlewareTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs index 4a7d65a86..04c41ff74 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetCommandMiddlewareTests.cs @@ -10,32 +10,35 @@ using System; using System.IO; using System.Threading.Tasks; using FakeItEasy; -using Squidex.Domain.Apps.Write.Assets.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Tasks; using Xunit; -namespace Squidex.Domain.Apps.Write.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public class AssetCommandMiddlewareTests : HandlerTestBase { private readonly IAssetThumbnailGenerator assetThumbnailGenerator = A.Fake(); private readonly IAssetStore assetStore = A.Fake(); - private readonly AssetCommandMiddleware sut; - private readonly AssetDomainObject asset; private readonly Guid assetId = Guid.NewGuid(); private readonly Stream stream = new MemoryStream(); private readonly ImageInfo image = new ImageInfo(2048, 2048); + private readonly AssetDomainObject asset = new AssetDomainObject(); private readonly AssetFile file; + private readonly AssetCommandMiddleware sut; + + protected override Guid Id + { + get { return assetId; } + } public AssetCommandMiddlewareTests() { file = new AssetFile("my-image.png", "image/png", 1024, () => stream); - asset = new AssetDomainObject(assetId, -1); - sut = new AssetCommandMiddleware(Handler, assetStore, assetThumbnailGenerator); } @@ -54,8 +57,8 @@ namespace Squidex.Domain.Apps.Write.Assets Assert.Equal(assetId, context.Result>().IdOrValue); - VerifyStore(0, context.ContextId); - VerifyImageInfo(); + AssertAssetHasBeenUploaded(0, context.ContextId); + AssertAssetImageChecked(); } [Fact] @@ -73,8 +76,8 @@ namespace Squidex.Domain.Apps.Write.Assets await sut.HandleAsync(context); }); - VerifyStore(1, context.ContextId); - VerifyImageInfo(); + AssertAssetHasBeenUploaded(1, context.ContextId); + AssertAssetImageChecked(); } [Fact] @@ -105,7 +108,7 @@ namespace Squidex.Domain.Apps.Write.Assets private void CreateAsset() { - asset.Create(new CreateAsset { File = file }); + asset.Create(CreateCommand(new CreateAsset { File = file })); } private void SetupImageInfo() @@ -124,16 +127,20 @@ namespace Squidex.Domain.Apps.Write.Assets .Returns(TaskHelper.Done); } - private void VerifyImageInfo() + private void AssertAssetImageChecked() { - A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream)).MustHaveHappened(); + A.CallTo(() => assetThumbnailGenerator.GetImageInfoAsync(stream)) + .MustHaveHappened(); } - private void VerifyStore(long version, Guid commitId) + private void AssertAssetHasBeenUploaded(long version, Guid commitId) { - A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream)).MustHaveHappened(); - A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null)).MustHaveHappened(); - A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString())).MustHaveHappened(); + A.CallTo(() => assetStore.UploadTemporaryAsync(commitId.ToString(), stream)) + .MustHaveHappened(); + A.CallTo(() => assetStore.CopyTemporaryAsync(commitId.ToString(), assetId.ToString(), version, null)) + .MustHaveHappened(); + A.CallTo(() => assetStore.DeleteTemporaryAsync(commitId.ToString())) + .MustHaveHappened(); } } } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs similarity index 87% rename from tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetDomainObjectTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs index cbca77d3b..ff14541a9 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Assets/AssetDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetDomainObjectTests.cs @@ -8,33 +8,31 @@ using System; using System.IO; +using Squidex.Domain.Apps.Entities.Assets.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Assets; -using Squidex.Domain.Apps.Write.Assets.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; -using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Assets +namespace Squidex.Domain.Apps.Entities.Assets { public class AssetDomainObjectTests : HandlerTestBase { - private readonly AssetDomainObject sut; private readonly ImageInfo image = new ImageInfo(2048, 2048); + private readonly Guid assetId = Guid.NewGuid(); private readonly AssetFile file = new AssetFile("my-image.png", "image/png", 1024, () => new MemoryStream()); + private readonly AssetDomainObject sut = new AssetDomainObject(); - public Guid AssetId { get; } = Guid.NewGuid(); - - public AssetDomainObjectTests() + protected override Guid Id { - sut = new AssetDomainObject(AssetId, 0); + get { return assetId; } } [Fact] public void Create_should_throw_exception_if_created() { - sut.Create(new CreateAsset { File = file }); + CreateAsset(); Assert.Throws(() => { @@ -47,6 +45,8 @@ namespace Squidex.Domain.Apps.Write.Assets { sut.Create(CreateAssetCommand(new CreateAsset { File = file, ImageInfo = image })); + Assert.Equal(0, sut.State.FileVersion); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateAssetEvent(new AssetCreated @@ -90,6 +90,8 @@ namespace Squidex.Domain.Apps.Write.Assets sut.Update(CreateAssetCommand(new UpdateAsset { File = file, ImageInfo = image })); + Assert.Equal(1, sut.State.FileVersion); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateAssetEvent(new AssetUpdated @@ -132,6 +134,8 @@ namespace Squidex.Domain.Apps.Write.Assets sut.Rename(CreateAssetCommand(new RenameAsset { FileName = "my-new-image.png" })); + Assert.Equal("my-new-image.png", sut.State.FileName); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateAssetEvent(new AssetRenamed { FileName = "my-new-image.png" }) @@ -167,7 +171,7 @@ namespace Squidex.Domain.Apps.Write.Assets sut.Delete(CreateAssetCommand(new DeleteAsset())); - Assert.True(sut.IsDeleted); + Assert.True(sut.State.IsDeleted); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -178,34 +182,31 @@ namespace Squidex.Domain.Apps.Write.Assets private void CreateAsset() { sut.Create(CreateAssetCommand(new CreateAsset { File = file })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void UpdateAsset() { sut.Update(CreateAssetCommand(new UpdateAsset { File = file })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void DeleteAsset() { sut.Delete(CreateAssetCommand(new DeleteAsset())); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } protected T CreateAssetEvent(T @event) where T : AssetEvent { - @event.AssetId = AssetId; + @event.AssetId = assetId; return CreateEvent(@event); } protected T CreateAssetCommand(T command) where T : AssetAggregateCommand { - command.AssetId = AssetId; + command.AssetId = assetId; return CreateCommand(command); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Assets/Guards/GuardAssetTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs similarity index 94% rename from tests/Squidex.Domain.Apps.Write.Tests/Assets/Guards/GuardAssetTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs index aa34e4b0b..92d676354 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Assets/Guards/GuardAssetTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Assets/Guards/GuardAssetTests.cs @@ -6,11 +6,11 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Write.Assets.Commands; +using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Assets.Guards +namespace Squidex.Domain.Apps.Entities.Assets.Guards { public class GuardAssetTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs index 21d668f7e..96ade7d26 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -15,23 +15,20 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Contents.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public class ContentCommandMiddlewareTests : HandlerTestBase { - private readonly ContentCommandMiddleware sut; - private readonly ContentDomainObject content; private readonly ISchemaEntity schema = A.Fake(); private readonly IScriptEngine scriptEngine = A.Fake(); private readonly IAppProvider appProvider = A.Fake(); @@ -39,6 +36,13 @@ namespace Squidex.Domain.Apps.Write.Contents private readonly ClaimsPrincipal user = new ClaimsPrincipal(); private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.DE); private readonly Guid contentId = Guid.NewGuid(); + private readonly ContentDomainObject content = new ContentDomainObject(); + private readonly ContentCommandMiddleware sut; + + protected override Guid Id + { + get { return contentId; } + } private readonly NamedContentData invalidData = new NamedContentData() @@ -66,8 +70,6 @@ namespace Squidex.Domain.Apps.Write.Contents .AddField(new NumberField(2, "my-field2", Partitioning.Invariant, new NumberFieldProperties { IsRequired = false })); - content = new ContentDomainObject(contentId, -1); - sut = new ContentCommandMiddleware(Handler, appProvider, A.Dummy(), scriptEngine, A.Dummy()); A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); @@ -80,7 +82,7 @@ namespace Squidex.Domain.Apps.Write.Contents A.CallTo(() => schema.ScriptUpdate).Returns(""); A.CallTo(() => schema.ScriptDelete).Returns(""); - A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppName, SchemaId)).Returns((app, schema)); + A.CallTo(() => appProvider.GetAppWithSchemaAsync(AppId, SchemaId)).Returns((app, schema)); } [Fact] @@ -241,7 +243,7 @@ namespace Squidex.Domain.Apps.Write.Contents private void CreateContent() { - content.Create(new CreateContent { Data = data }); + content.Create(CreateCommand(new CreateContent { Data = data })); } } } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs similarity index 90% rename from tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentDomainObjectTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index 4e0c8d538..11f32b962 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -9,18 +9,16 @@ using System; using FluentAssertions; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public class ContentDomainObjectTests : HandlerTestBase { - private readonly ContentDomainObject sut; private readonly NamedContentData data = new NamedContentData() .AddField("field1", @@ -31,22 +29,24 @@ namespace Squidex.Domain.Apps.Write.Contents .AddField("field2", new ContentFieldData() .AddValue("iv", 2)); - private readonly NamedContentData patched; + private readonly Guid contentId = Guid.NewGuid(); + private readonly ContentDomainObject sut = new ContentDomainObject(); - public Guid ContentId { get; } = Guid.NewGuid(); + protected override Guid Id + { + get { return contentId; } + } public ContentDomainObjectTests() { patched = otherData.MergeInto(data); - - sut = new ContentDomainObject(ContentId, 0); } [Fact] public void Create_should_throw_exception_if_created() { - sut.Create(new CreateContent { Data = data }); + sut.Create(CreateCommand(new CreateContent { Data = data })); Assert.Throws(() => { @@ -196,7 +196,7 @@ namespace Squidex.Domain.Apps.Write.Contents sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = Status.Published })); - Assert.Equal(Status.Published, sut.Status); + Assert.Equal(Status.Published, sut.State.Status); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -232,7 +232,7 @@ namespace Squidex.Domain.Apps.Write.Contents sut.Delete(CreateContentCommand(new DeleteContent())); - Assert.True(sut.IsDeleted); + Assert.True(sut.State.IsDeleted); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -243,41 +243,37 @@ namespace Squidex.Domain.Apps.Write.Contents private void CreateContent() { sut.Create(CreateContentCommand(new CreateContent { Data = data })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void UpdateContent() { sut.Update(CreateContentCommand(new UpdateContent { Data = data })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void ChangeStatus(Status status) { sut.ChangeStatus(CreateContentCommand(new ChangeContentStatus { Status = status })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void DeleteContent() { sut.Delete(CreateContentCommand(new DeleteContent())); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } protected T CreateContentEvent(T @event) where T : ContentEvent { - @event.ContentId = ContentId; + @event.ContentId = contentId; return CreateEvent(@event); } protected T CreateContentCommand(T command) where T : ContentCommand { - command.ContentId = ContentId; + command.ContentId = contentId; return CreateCommand(command); } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs similarity index 91% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/ContentQueryServiceTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index e2cea0ad6..ecb503b83 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -14,15 +14,15 @@ using FakeItEasy; using Microsoft.OData.UriParser; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Contents.Edm; -using Squidex.Domain.Apps.Read.Contents.Repositories; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Contents.Edm; +using Squidex.Domain.Apps.Entities.Contents.Repositories; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Security; using Xunit; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents { public class ContentQueryServiceTests { @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Read.Contents [Fact] public async Task Should_return_schema_from_id_if_string_is_guid() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); var result = await sut.FindSchemaAsync(app, schemaId.ToString()); @@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Read.Contents [Fact] public async Task Should_return_schema_from_name_if_string_not_guid() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, "my-schema", false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema", false)) .Returns(schema); var result = await sut.FindSchemaAsync(app, "my-schema"); @@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Read.Contents [Fact] public async Task Should_throw_if_schema_not_found() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, "my-schema", false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, "my-schema", false)) .Returns((ISchemaEntity)null); await Assert.ThrowsAsync(() => sut.FindSchemaAsync(app, "my-schema")); @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Read.Contents [Fact] public async Task Should_return_content_from_repository_and_transform() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) .Returns(content); @@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Read.Contents [Fact] public async Task Should_throw_if_content_to_find_does_not_exist() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.FindContentAsync(app, schema, contentId)) .Returns((IContentEntity)null); @@ -190,7 +190,7 @@ namespace Squidex.Domain.Apps.Read.Contents private void SetupFakeWithIdQuery(Status[] status, HashSet ids) { - A.CallTo(() => appProvider.GetSchemaAsync(appName, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), ids)) .Returns(new List { content }); @@ -200,7 +200,7 @@ namespace Squidex.Domain.Apps.Read.Contents private void SetupFakeWithOdataQuery(Status[] status) { - A.CallTo(() => appProvider.GetSchemaAsync(appName, schemaId, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, schemaId, false)) .Returns(schema); A.CallTo(() => contentRepository.QueryAsync(app, schema, A.That.IsSameSequenceAs(status), A.Ignored)) .Returns(new List { content }); diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs index db70d4f1f..62379fac4 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/GraphQLTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTests.cs @@ -20,18 +20,17 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Assets.Repositories; -using Squidex.Domain.Apps.Read.Contents.GraphQL; -using Squidex.Domain.Apps.Read.Contents.TestData; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Assets.Repositories; +using Squidex.Domain.Apps.Entities.Contents.TestData; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1311 // Static readonly fields must begin with upper-case letter -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public class GraphQLTests { @@ -85,7 +84,7 @@ namespace Squidex.Domain.Apps.Read.Contents var allSchemas = new List { schema }; - A.CallTo(() => appProvider.GetSchemasAsync(appName)).Returns(allSchemas); + A.CallTo(() => appProvider.GetSchemasAsync(appId)).Returns(allSchemas); sut = new CachingGraphQLService(cache, appProvider, assetRepository, contentQuery, new FakeUrlGenerator()); } @@ -391,7 +390,7 @@ namespace Squidex.Domain.Apps.Read.Contents }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) .Returns((schema, content)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); @@ -483,7 +482,7 @@ namespace Squidex.Domain.Apps.Read.Contents var refContents = new List { contentRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) .Returns((schema, content)); A.CallTo(() => contentQuery.QueryWithCountAsync(app, schema.Id.ToString(), user, false, A>.That.Matches(x => x.Contains(contentRefId)))) @@ -543,7 +542,7 @@ namespace Squidex.Domain.Apps.Read.Contents var refAssets = new List { assetRef }; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) .Returns((schema, content)); A.CallTo(() => assetRepository.QueryAsync(app.Id, null, A>.That.Matches(x => x.Contains(assetRefId)), null, int.MaxValue, 0)) @@ -602,7 +601,7 @@ namespace Squidex.Domain.Apps.Read.Contents }} }}"; - A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId)) + A.CallTo(() => contentQuery.FindContentAsync(app, schema.Id.ToString(), user, contentId, EtagVersion.Any)) .Returns((schema, content)); var result = await sut.QueryAsync(app, user, new GraphQLQuery { Query = query }); diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs similarity index 94% rename from tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index b0f78570d..49a9c465e 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/Guard/GuardContentTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -7,12 +7,12 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Write.Contents.Commands; -using Squidex.Domain.Apps.Write.Contents.Guards; +using Squidex.Domain.Apps.Entities.Contents.Commands; +using Squidex.Domain.Apps.Entities.Contents.Guards; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Contents.Guard +namespace Squidex.Domain.Apps.Entities.Contents.Guard { public class GuardContentTests { diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/OData/ODataQueryTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/OData/ODataQueryTests.cs index 099b61d0f..3af662a17 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/ODataQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/OData/ODataQueryTests.cs @@ -17,16 +17,16 @@ using MongoDB.Driver; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Contents.Edm; -using Squidex.Domain.Apps.Read.MongoDb.Contents; -using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Contents.Edm; +using Squidex.Domain.Apps.Entities.MongoDb.Contents; +using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.MongoDb; using Xunit; -namespace Squidex.Domain.Apps.Read.Contents +namespace Squidex.Domain.Apps.Entities.Contents.OData { public class ODataQueryTests { @@ -389,4 +389,4 @@ namespace Squidex.Domain.Apps.Read.Contents return query; } } -} +} \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs similarity index 92% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs index e1373ad67..261c1d4f0 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeAssetEntity.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeAssetEntity.cs @@ -8,10 +8,10 @@ using System; using NodaTime; -using Squidex.Domain.Apps.Read.Assets; +using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.TestData +namespace Squidex.Domain.Apps.Entities.Contents.TestData { public sealed class FakeAssetEntity : IAssetEntity { diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeContentEntity.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeContentEntity.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs index b2dc5cecd..fe06f5165 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeContentEntity.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeContentEntity.cs @@ -11,19 +11,22 @@ using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; -namespace Squidex.Domain.Apps.Read.Contents.TestData +namespace Squidex.Domain.Apps.Entities.Contents.TestData { public sealed class FakeContentEntity : IContentEntity { public Guid Id { get; set; } + public Guid AppId { get; set; } public long Version { get; set; } public Instant Created { get; set; } + public Instant LastModified { get; set; } public RefToken CreatedBy { get; set; } + public RefToken LastModifiedBy { get; set; } public NamedContentData Data { get; set; } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs similarity index 82% rename from tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs index d501bcd4c..900d01fc1 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Contents/TestData/FakeUrlGenerator.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs @@ -6,12 +6,12 @@ // All rights reserved. // ========================================================================== -using Squidex.Domain.Apps.Read.Apps; -using Squidex.Domain.Apps.Read.Assets; -using Squidex.Domain.Apps.Read.Contents.GraphQL; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents.GraphQL; +using Squidex.Domain.Apps.Entities.Schemas; -namespace Squidex.Domain.Apps.Read.Contents.TestData +namespace Squidex.Domain.Apps.Entities.Contents.TestData { public sealed class FakeUrlGenerator : IGraphQLUrlGenerator { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs similarity index 95% rename from tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs index fe3487cce..fed8f64ba 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Actions/WebhookActionTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Actions/WebhookActionTests.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Rules.Actions; using Xunit; -namespace Squidex.Domain.Apps.Write.Rules.Guards.Actions +namespace Squidex.Domain.Apps.Entities.Rules.Guards.Actions { public sealed class WebhookActionTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs similarity index 95% rename from tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 76975b496..81d5f20fe 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/GuardRuleTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -13,15 +13,14 @@ using FakeItEasy; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Rules.Commands; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Rules.Guards +namespace Squidex.Domain.Apps.Entities.Rules.Guards { public class GuardRuleTests { @@ -32,7 +31,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards public GuardRuleTests() { - A.CallTo(() => appProvider.GetSchemaAsync(appId.Name, A.Ignored, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId.Id, A.Ignored, false)) .Returns(A.Fake()); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs similarity index 81% rename from tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs index 7e5546070..f24ecd9cd 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -11,21 +11,20 @@ using System.Collections.Immutable; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; +using Squidex.Domain.Apps.Entities.Schemas; using Xunit; -namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers +namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers { public class ContentChangedTriggerTests { private readonly IAppProvider appProvider = A.Fake(); - private readonly string appName = "my-app"; + private readonly Guid appId = Guid.NewGuid(); [Fact] public async Task Should_add_error_if_schemas_ids_are_not_valid() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, A.Ignored, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(Task.FromResult(null)); var trigger = new ContentChangedTrigger @@ -35,7 +34,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers ) }; - var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); Assert.NotEmpty(errors); } @@ -45,7 +44,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers { var trigger = new ContentChangedTrigger(); - var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); Assert.Empty(errors); } @@ -58,7 +57,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers Schemas = ImmutableList.Empty }; - var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); Assert.Empty(errors); } @@ -66,7 +65,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers [Fact] public async Task Should_not_add_error_if_schemas_ids_are_valid() { - A.CallTo(() => appProvider.GetSchemaAsync(appName, A.Ignored, false)) + A.CallTo(() => appProvider.GetSchemaAsync(appId, A.Ignored, false)) .Returns(A.Fake()); var trigger = new ContentChangedTrigger @@ -76,7 +75,7 @@ namespace Squidex.Domain.Apps.Write.Rules.Guards.Triggers ) }; - var errors = await RuleTriggerValidator.ValidateAsync(appName, trigger, appProvider); + var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); Assert.Empty(errors); } diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs similarity index 83% rename from tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs index c8055837d..61ab2de50 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleCommandMiddlewareTests.cs @@ -12,31 +12,33 @@ using FakeItEasy; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Triggers; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Rules.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public class RuleCommandMiddlewareTests : HandlerTestBase { private readonly IAppProvider appProvider = A.Fake(); - private readonly RuleCommandMiddleware sut; - private readonly RuleDomainObject rule; + private readonly RuleDomainObject rule = new RuleDomainObject(); private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; private readonly Guid ruleId = Guid.NewGuid(); + private readonly RuleCommandMiddleware sut; + + protected override Guid Id + { + get { return ruleId; } + } public RuleCommandMiddlewareTests() { - A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, A.Ignored, false)) + A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, A.Ignored, false)) .Returns(A.Fake()); - rule = new RuleDomainObject(ruleId, -1); - sut = new RuleCommandMiddleware(Handler, appProvider); } @@ -106,12 +108,12 @@ namespace Squidex.Domain.Apps.Write.Rules private void DisableRule() { - rule.Disable(new DisableRule()); + rule.Disable(CreateCommand(new DisableRule())); } private void CreateRule() { - rule.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + rule.Create(CreateCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); } } } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleDequeuerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs similarity index 97% rename from tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleDequeuerTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs index 8a43c6345..3737294be 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleDequeuerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs @@ -12,13 +12,13 @@ using FakeItEasy; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; -using Squidex.Domain.Apps.Read.Rules.Repositories; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Infrastructure.Log; using Xunit; #pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public class RuleDequeuerTests { @@ -26,9 +26,9 @@ namespace Squidex.Domain.Apps.Read.Rules private readonly ISemanticLog log = A.Fake(); private readonly IAppProvider appProvider = A.Fake(); private readonly IRuleEventRepository ruleEventRepository = A.Fake(); + private readonly Instant now = SystemClock.Instance.GetCurrentInstant(); private readonly RuleService ruleService = A.Fake(); private readonly RuleDequeuer sut; - private readonly Instant now = SystemClock.Instance.GetCurrentInstant(); public RuleDequeuerTests() { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs similarity index 85% rename from tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs index b6029798c..0b11c525d 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Rules/RuleDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDomainObjectTests.cs @@ -11,32 +11,30 @@ using System.Collections.Immutable; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.Rules.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Rules; -using Squidex.Domain.Apps.Write.Rules.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public class RuleDomainObjectTests : HandlerTestBase { + private readonly Guid ruleId = Guid.NewGuid(); private readonly RuleTrigger ruleTrigger = new ContentChangedTrigger(); private readonly RuleAction ruleAction = new WebhookAction { Url = new Uri("https://squidex.io") }; - private readonly RuleDomainObject sut; + private readonly RuleDomainObject sut = new RuleDomainObject(); - public Guid RuleId { get; } = Guid.NewGuid(); - - public RuleDomainObjectTests() + protected override Guid Id { - sut = new RuleDomainObject(RuleId, 0); + get { return ruleId; } } [Fact] public void Create_should_throw_exception_if_created() { - sut.Create(new CreateRule { Trigger = ruleTrigger, Action = ruleAction }); + sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); Assert.Throws(() => { @@ -51,6 +49,11 @@ namespace Squidex.Domain.Apps.Write.Rules sut.Create(CreateRuleCommand(command)); + Assert.Equal(AppId, sut.State.AppId); + + Assert.Same(ruleTrigger, sut.State.RuleDef.Trigger); + Assert.Same(ruleAction, sut.State.RuleDef.Action); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateRuleEvent(new RuleCreated { Trigger = ruleTrigger, Action = ruleAction }) @@ -97,6 +100,9 @@ namespace Squidex.Domain.Apps.Write.Rules sut.Update(CreateRuleCommand(command)); + Assert.Same(newTrigger, sut.State.RuleDef.Trigger); + Assert.Same(newAction, sut.State.RuleDef.Action); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateRuleEvent(new RuleUpdated { Trigger = newTrigger, Action = newAction }) @@ -133,6 +139,8 @@ namespace Squidex.Domain.Apps.Write.Rules sut.Enable(CreateRuleCommand(command)); + Assert.True(sut.State.RuleDef.IsEnabled); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateRuleEvent(new RuleEnabled()) @@ -169,6 +177,8 @@ namespace Squidex.Domain.Apps.Write.Rules sut.Disable(CreateRuleCommand(command)); + Assert.False(sut.State.RuleDef.IsEnabled); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateRuleEvent(new RuleDisabled()) @@ -203,6 +213,8 @@ namespace Squidex.Domain.Apps.Write.Rules sut.Delete(CreateRuleCommand(new DeleteRule())); + Assert.True(sut.State.IsDeleted); + sut.GetUncomittedEvents() .ShouldHaveSameEvents( CreateRuleEvent(new RuleDeleted()) @@ -212,27 +224,25 @@ namespace Squidex.Domain.Apps.Write.Rules private void CreateRule() { sut.Create(CreateRuleCommand(new CreateRule { Trigger = ruleTrigger, Action = ruleAction })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void DeleteRule() { sut.Delete(CreateRuleCommand(new DeleteRule())); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } protected T CreateRuleEvent(T @event) where T : RuleEvent { - @event.RuleId = RuleId; + @event.RuleId = ruleId; return CreateEvent(@event); } protected T CreateRuleCommand(T command) where T : RuleAggregateCommand { - command.RuleId = RuleId; + command.RuleId = ruleId; return CreateCommand(command); } diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs similarity index 95% rename from tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index 93a598752..fb6b8ce31 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Rules/RuleEnqueuerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -15,13 +15,13 @@ using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Actions; using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Read.Rules.Repositories; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; using Xunit; -namespace Squidex.Domain.Apps.Read.Rules +namespace Squidex.Domain.Apps.Entities.Rules { public class RuleEnqueuerTests { @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Rules A.CallTo(() => ruleEntity2.RuleDef).Returns(rule2); A.CallTo(() => ruleEntity3.RuleDef).Returns(rule3); - A.CallTo(() => appProvider.GetRulesAsync(appId.Name)) + A.CallTo(() => appProvider.GetRulesAsync(appId.Id)) .Returns(new List { ruleEntity1, ruleEntity2, ruleEntity3 }); A.CallTo(() => ruleService.CreateJob(rule1, @event)) diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs index c8c1a46c1..9f4f4c7b8 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class AssetsFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs index 4469a2dcf..0acb80581 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/BooleanFieldPropertiesTests.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class BooleanFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs index 14bd29f42..24791ae69 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs @@ -15,7 +15,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class DateTimeFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs index 8f3967ee3..ae2e794f5 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/GeolocationFieldPropertiesTests.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class GeolocationFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs similarity index 91% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs index c4415bf1d..52e4601ed 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/JsonFieldPropertiesTests.cs @@ -10,7 +10,7 @@ using System.Linq; using Squidex.Domain.Apps.Core.Schemas; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class JsonFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs index 199261ba4..add452cb4 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs @@ -14,7 +14,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class NumberFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs index 2f721bf05..8465de44c 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class ReferencesFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs index 19c306680..3d6585883 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs @@ -14,7 +14,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class StringFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs similarity index 93% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs index 77dec4d89..6e413d0c9 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs @@ -13,7 +13,7 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas.Guards.FieldProperties +namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties { public class TagsFieldPropertiesTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaFieldTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs similarity index 98% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaFieldTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs index 19162a358..9770155e8 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs @@ -8,13 +8,13 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Schemas.Guards +namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public class GuardSchemaFieldTests { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs similarity index 94% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 3b9ac5b4b..be2bf2775 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -12,15 +12,13 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; +using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore -namespace Squidex.Domain.Apps.Write.Schemas.Guards +namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public class GuardSchemaTests { @@ -35,7 +33,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards .AddField(new StringField(1, "field1", Partitioning.Invariant)) .AddField(new StringField(2, "field2", Partitioning.Invariant)); - A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, "new-schema", false)) + A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, "new-schema", false)) .Returns(Task.FromResult(null)); } @@ -50,7 +48,7 @@ namespace Squidex.Domain.Apps.Write.Schemas.Guards [Fact] public Task CanCreate_should_throw_exception_if_name_already_in_use() { - A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, "new-schema", false)) + A.CallTo(() => appProvider.GetSchemaAsync(A.Ignored, "new-schema", false)) .Returns(Task.FromResult(A.Fake())); var command = new CreateSchema { AppId = appId, Name = "new-schema" }; diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandMiddlewareTests.cs similarity index 94% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaCommandMiddlewareTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandMiddlewareTests.cs index 9ad2b24fc..1cd2a9a1d 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaCommandMiddlewareTests.cs @@ -11,15 +11,13 @@ using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Domain.Apps.Read; -using Squidex.Domain.Apps.Read.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { public class SchemaCommandMiddlewareTests : HandlerTestBase { @@ -29,13 +27,18 @@ namespace Squidex.Domain.Apps.Write.Schemas private readonly FieldRegistry registry = new FieldRegistry(new TypeNameRegistry()); private readonly string fieldName = "age"; + protected override Guid Id + { + get { return SchemaId; } + } + public SchemaCommandMiddlewareTests() { - schema = new SchemaDomainObject(SchemaId, -1, registry); + schema = new SchemaDomainObject(registry); sut = new SchemaCommandMiddleware(Handler, appProvider); - A.CallTo(() => appProvider.GetSchemaAsync(AppName, SchemaName, false)) + A.CallTo(() => appProvider.GetSchemaAsync(AppId, SchemaName, false)) .Returns((ISchemaEntity)null); } @@ -51,7 +54,7 @@ namespace Squidex.Domain.Apps.Write.Schemas Assert.Equal(SchemaId, context.Result>().IdOrValue); - A.CallTo(() => appProvider.GetSchemaAsync(AppName, SchemaName, false)).MustHaveHappened(); + A.CallTo(() => appProvider.GetSchemaAsync(AppId, SchemaName, false)).MustHaveHappened(); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaDomainObjectTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs similarity index 89% rename from tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaDomainObjectTests.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs index 69fd68ebd..5275eafc5 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/SchemaDomainObjectTests.cs @@ -6,17 +6,17 @@ // All rights reserved. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Schemas; -using Squidex.Domain.Apps.Write.Schemas.Commands; -using Squidex.Domain.Apps.Write.TestHelpers; using Squidex.Infrastructure; -using Squidex.Infrastructure.Commands; using Xunit; -namespace Squidex.Domain.Apps.Write.Schemas +namespace Squidex.Domain.Apps.Entities.Schemas { public class SchemaDomainObjectTests : HandlerTestBase { @@ -24,19 +24,24 @@ namespace Squidex.Domain.Apps.Write.Schemas private readonly NamedId fieldId; private readonly SchemaDomainObject sut; + protected override Guid Id + { + get { return SchemaId; } + } + public SchemaDomainObjectTests() { fieldId = new NamedId(1, fieldName); var fieldRegistry = new FieldRegistry(new TypeNameRegistry()); - sut = new SchemaDomainObject(SchemaId, 0, fieldRegistry); + sut = new SchemaDomainObject(fieldRegistry); } [Fact] public void Create_should_throw_exception_if_created() { - sut.Create(new CreateSchema { Name = SchemaName }); + sut.Create(CreateCommand(new CreateSchema { Name = SchemaName })); Assert.Throws(() => { @@ -49,9 +54,12 @@ namespace Squidex.Domain.Apps.Write.Schemas { var properties = new SchemaProperties(); - sut.Create(CreateCommand(new CreateSchema { Name = SchemaName, Properties = properties })); + sut.Create(CreateCommand(new CreateSchema { Name = SchemaName, SchemaId = SchemaId, Properties = properties })); + + Assert.Equal(AppId, sut.State.AppId); - Assert.Equal(SchemaName, sut.Schema.Name); + Assert.Equal(SchemaName, sut.State.Name); + Assert.Equal(SchemaName, sut.State.SchemaDef.Name); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -74,7 +82,10 @@ namespace Squidex.Domain.Apps.Write.Schemas var @event = (SchemaCreated)sut.GetUncomittedEvents().Single().Payload; - Assert.Equal(SchemaName, sut.Schema.Name); + Assert.Equal(AppId, sut.State.AppId); + Assert.Equal(SchemaName, sut.State.Name); + Assert.Equal(SchemaName, sut.State.SchemaDef.Name); + Assert.Equal(2, @event.Fields.Count); } @@ -108,7 +119,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.Update(CreateCommand(new UpdateSchema { Properties = properties })); - Assert.Equal(properties, sut.Schema.Properties); + Assert.Equal(properties, sut.State.SchemaDef.Properties); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -192,10 +203,10 @@ namespace Squidex.Domain.Apps.Write.Schemas CreateSchema(); - sut.Add(new AddField { Name = "field1", Properties = ValidProperties() }); - sut.Add(new AddField { Name = "field2", Properties = ValidProperties() }); + sut.Add(CreateCommand(new AddField { Name = "field1", Properties = ValidProperties() })); + sut.Add(CreateCommand(new AddField { Name = "field2", Properties = ValidProperties() })); - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); sut.Reorder(CreateCommand(new ReorderFields { FieldIds = fieldIds })); @@ -233,7 +244,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.Publish(CreateCommand(new PublishSchema())); - Assert.True(sut.Schema.IsPublished); + Assert.True(sut.State.SchemaDef.IsPublished); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -270,7 +281,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.Unpublish(CreateCommand(new UnpublishSchema())); - Assert.False(sut.Schema.IsPublished); + Assert.False(sut.State.SchemaDef.IsPublished); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -306,7 +317,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.Delete(CreateCommand(new DeleteSchema())); - Assert.True(sut.IsDeleted); + Assert.True(sut.State.IsDeleted); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -344,7 +355,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.Add(CreateCommand(new AddField { Name = fieldName, Properties = properties })); - Assert.Equal(properties, sut.Schema.FieldsById[1].RawProperties); + Assert.Equal(properties, sut.State.SchemaDef.FieldsById[1].RawProperties); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -383,7 +394,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.UpdateField(CreateCommand(new UpdateField { FieldId = 1, Properties = properties })); - Assert.Equal(properties, sut.Schema.FieldsById[1].RawProperties); + Assert.Equal(properties, sut.State.SchemaDef.FieldsById[1].RawProperties); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -420,7 +431,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.LockField(CreateCommand(new LockField { FieldId = 1 })); - Assert.False(sut.Schema.FieldsById[1].IsDisabled); + Assert.False(sut.State.SchemaDef.FieldsById[1].IsDisabled); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -457,7 +468,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.HideField(CreateCommand(new HideField { FieldId = 1 })); - Assert.True(sut.Schema.FieldsById[1].IsHidden); + Assert.True(sut.State.SchemaDef.FieldsById[1].IsHidden); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -495,7 +506,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.HideField(CreateCommand(new HideField { FieldId = 1 })); sut.ShowField(CreateCommand(new ShowField { FieldId = 1 })); - Assert.False(sut.Schema.FieldsById[1].IsHidden); + Assert.False(sut.State.SchemaDef.FieldsById[1].IsHidden); sut.GetUncomittedEvents().Skip(1) .ShouldHaveSameEvents( @@ -532,7 +543,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.DisableField(CreateCommand(new DisableField { FieldId = 1 })); - Assert.True(sut.Schema.FieldsById[1].IsDisabled); + Assert.True(sut.State.SchemaDef.FieldsById[1].IsDisabled); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -570,7 +581,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.DisableField(CreateCommand(new DisableField { FieldId = 1 })); sut.EnableField(CreateCommand(new EnableField { FieldId = 1 })); - Assert.False(sut.Schema.FieldsById[1].IsDisabled); + Assert.False(sut.State.SchemaDef.FieldsById[1].IsDisabled); sut.GetUncomittedEvents().Skip(1) .ShouldHaveSameEvents( @@ -607,7 +618,7 @@ namespace Squidex.Domain.Apps.Write.Schemas sut.DeleteField(CreateCommand(new DeleteField { FieldId = 1 })); - Assert.False(sut.Schema.FieldsById.ContainsKey(1)); + Assert.False(sut.State.SchemaDef.FieldsById.ContainsKey(1)); sut.GetUncomittedEvents() .ShouldHaveSameEvents( @@ -617,30 +628,26 @@ namespace Squidex.Domain.Apps.Write.Schemas private void CreateField() { - sut.Add(new AddField { Name = fieldName, Properties = new NumberFieldProperties() }); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.Add(CreateCommand(new AddField { Name = fieldName, Properties = new NumberFieldProperties() })); + sut.ClearUncommittedEvents(); } private void CreateSchema() { sut.Create(CreateCommand(new CreateSchema { Name = SchemaName })); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void PublishSchema() { sut.Publish(CreateCommand(new PublishSchema())); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private void DeleteSchema() { sut.Delete(CreateCommand(new DeleteSchema())); - - ((IAggregate)sut).ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); } private static StringFieldProperties ValidProperties() diff --git a/tests/Squidex.Domain.Apps.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj similarity index 76% rename from tests/Squidex.Domain.Apps.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj rename to tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index 8b14abdf4..84be716c4 100644 --- a/tests/Squidex.Domain.Apps.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.0 - Squidex.Domain.Apps.Read + Squidex.Domain.Apps.Entities @@ -12,13 +12,14 @@ + + - - + - + diff --git a/tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/AssertHelper.cs b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AssertHelper.cs similarity index 96% rename from tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/AssertHelper.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AssertHelper.cs index f9e889f95..6003f51c4 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/AssertHelper.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AssertHelper.cs @@ -11,7 +11,7 @@ using System.Linq; using FluentAssertions; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Write.TestHelpers +namespace Squidex.Domain.Apps.Entities.TestHelpers { public static class AssertHelper { diff --git a/tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/HandlerTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs similarity index 81% rename from tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/HandlerTestBase.cs rename to tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs index 2425a7e86..b3b48dc28 100644 --- a/tests/Squidex.Domain.Apps.Write.Tests/TestHelpers/HandlerTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/HandlerTestBase.cs @@ -8,15 +8,17 @@ using System; using System.Threading.Tasks; +using FakeItEasy; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.States; #pragma warning disable IDE0019 // Use pattern matching -namespace Squidex.Domain.Apps.Write.TestHelpers +namespace Squidex.Domain.Apps.Entities.TestHelpers { - public abstract class HandlerTestBase where T : DomainObjectBase + public abstract class HandlerTestBase where T : IDomainObject { private sealed class MockupHandler : IAggregateHandler { @@ -33,7 +35,17 @@ namespace Squidex.Domain.Apps.Write.TestHelpers IsUpdated = false; } - public async Task CreateAsync(CommandContext context, Func creator) where V : class, IAggregate + public Task CreateSyncedAsync(CommandContext context, Func creator) where V : class, IDomainObject + { + return CreateAsync(context, creator); + } + + public Task UpdateSyncedAsync(CommandContext context, Func creator) where V : class, IDomainObject + { + return UpdateAsync(context, creator); + } + + public async Task CreateAsync(CommandContext context, Func creator) where V : class, IDomainObject { IsCreated = true; @@ -44,7 +56,7 @@ namespace Squidex.Domain.Apps.Write.TestHelpers return @do; } - public async Task UpdateAsync(CommandContext context, Func updater) where V : class, IAggregate + public async Task UpdateAsync(CommandContext context, Func updater) where V : class, IDomainObject { IsUpdated = true; @@ -64,6 +76,8 @@ namespace Squidex.Domain.Apps.Write.TestHelpers protected Guid SchemaId { get; } = Guid.NewGuid(); + protected abstract Guid Id { get; } + protected string AppName { get; } = "my-app"; protected string SchemaName { get; } = "my-schema"; @@ -92,6 +106,7 @@ namespace Squidex.Domain.Apps.Write.TestHelpers { handler.Init(domainObject); + await domainObject.ActivateAsync(Id, A.Fake>()); await action(domainObject); if (!handler.IsCreated && shouldCreate) @@ -104,6 +119,7 @@ namespace Squidex.Domain.Apps.Write.TestHelpers { handler.Init(domainObject); + await domainObject.ActivateAsync(Id, A.Fake>()); await action(domainObject); if (!handler.IsUpdated && shouldUpdate) diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs deleted file mode 100644 index acb390b7e..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Apps/AppEventTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ========================================================================== -// AppEventTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Events; -using Squidex.Domain.Apps.Events.Apps; -using Squidex.Domain.Apps.Events.Apps.Old; -using Squidex.Domain.Apps.Write.TestHelpers; -using Squidex.Infrastructure; -using Xunit; - -#pragma warning disable CS0612 // Type or member is obsolete - -namespace Squidex.Domain.Apps.Write.Apps -{ - public class AppEventTests - { - private readonly RefToken actor = new RefToken("User", Guid.NewGuid().ToString()); - private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); - - [Fact] - public void Should_migrate_client_changed_as_reader_to_client_updated() - { - var source = CreateEvent(new AppClientChanged { IsReader = true }); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Reader })); - } - - [Fact] - public void Should_migrate_client_changed_as_writer_to_client_updated() - { - var source = CreateEvent(new AppClientChanged { IsReader = false }); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new AppClientUpdated { Permission = AppClientPermission.Editor })); - } - - private T CreateEvent(T contentEvent) where T : AppEvent - { - contentEvent.Actor = actor; - contentEvent.AppId = appId; - - return contentEvent; - } - } -} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentEventTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentEventTests.cs deleted file mode 100644 index dcdfe4fee..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentEventTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ========================================================================== -// SchemaEventTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Domain.Apps.Events.Contents.Old; -using Squidex.Domain.Apps.Write.TestHelpers; -using Squidex.Infrastructure; -using Xunit; - -#pragma warning disable CS0612 // Type or member is obsolete - -namespace Squidex.Domain.Apps.Write.Contents -{ - public class ContentEventTests - { - private readonly RefToken actor = new RefToken("User", Guid.NewGuid().ToString()); - private readonly NamedId appId = new NamedId(Guid.NewGuid(), "my-app"); - private readonly NamedId schemaId = new NamedId(Guid.NewGuid(), "my-schema"); - private readonly Guid contentId = Guid.NewGuid(); - - [Fact] - public void Should_migrate_content_published_to_content_status_changed() - { - var source = CreateEvent(new ContentPublished()); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Published })); - } - - [Fact] - public void Should_migrate_content_unpublished_to_content_status_changed() - { - var source = CreateEvent(new ContentUnpublished()); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Draft })); - } - - [Fact] - public void Should_migrate_content_restored_to_content_status_changed() - { - var source = CreateEvent(new ContentRestored()); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Draft })); - } - - [Fact] - public void Should_migrate_content_archived_to_content_status_changed() - { - var source = CreateEvent(new ContentArchived()); - - source.Migrate().ShouldBeSameEvent(CreateEvent(new ContentStatusChanged { Status = Status.Archived })); - } - - private T CreateEvent(T contentEvent) where T : ContentEvent - { - contentEvent.Actor = actor; - contentEvent.AppId = appId; - contentEvent.SchemaId = schemaId; - contentEvent.ContentId = contentId; - - return contentEvent; - } - } -} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentVersionLoaderTests.cs b/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentVersionLoaderTests.cs deleted file mode 100644 index 30bc8593d..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Contents/ContentVersionLoaderTests.cs +++ /dev/null @@ -1,140 +0,0 @@ -// ========================================================================== -// ContentVersionLoaderTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using FakeItEasy; -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Domain.Apps.Events.Contents; -using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; -using Squidex.Infrastructure.States; -using Xunit; - -namespace Squidex.Domain.Apps.Write.Contents -{ - public class ContentVersionLoaderTests - { - private readonly IEventStore eventStore = A.Fake(); - private readonly IEventDataFormatter formatter = A.Fake(); - private readonly IStreamNameResolver nameResolver = A.Fake(); - private readonly Guid id = Guid.NewGuid(); - private readonly Guid appId = Guid.NewGuid(); - private readonly string streamName = Guid.NewGuid().ToString(); - private readonly ContentVersionLoader sut; - - public ContentVersionLoaderTests() - { - A.CallTo(() => nameResolver.GetStreamName(typeof(ContentDomainObject), id.ToString())) - .Returns(streamName); - - sut = new ContentVersionLoader(eventStore, nameResolver, formatter); - } - - [Fact] - public async Task Should_throw_exception_when_event_store_returns_no_events() - { - A.CallTo(() => eventStore.GetEventsAsync(streamName, 0)) - .Returns(new List()); - - await Assert.ThrowsAsync(() => sut.LoadAsync(appId, id, -1)); - } - - [Fact] - public async Task Should_throw_exception_when_version_not_found() - { - A.CallTo(() => eventStore.GetEventsAsync(streamName, 0)) - .Returns(new List()); - - await Assert.ThrowsAsync(() => sut.LoadAsync(appId, id, 3)); - } - - [Fact] - public async Task Should_throw_exception_when_content_is_from_another_event() - { - var eventData1 = new EventData(); - - var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId(Guid.NewGuid(), "my-app") }; - - var events = new List - { - new StoredEvent("0", 0, eventData1) - }; - - A.CallTo(() => eventStore.GetEventsAsync(streamName, 0)) - .Returns(events); - - A.CallTo(() => formatter.Parse(eventData1, true)) - .Returns(new Envelope(event1)); - - await Assert.ThrowsAsync(() => sut.LoadAsync(appId, id, 0)); - } - - [Fact] - public async Task Should_load_content_from_created_event() - { - var eventData1 = new EventData(); - var eventData2 = new EventData(); - - var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId(appId, "my-app") }; - var event2 = new ContentStatusChanged(); - - var events = new List - { - new StoredEvent("0", 0, eventData1), - new StoredEvent("1", 1, eventData2) - }; - - A.CallTo(() => eventStore.GetEventsAsync(streamName, 0)) - .Returns(events); - - A.CallTo(() => formatter.Parse(eventData1, true)) - .Returns(new Envelope(event1)); - A.CallTo(() => formatter.Parse(eventData2, true)) - .Returns(new Envelope(event2)); - - var data = await sut.LoadAsync(appId, id, 3); - - Assert.Same(event1.Data, data); - } - - [Fact] - public async Task Should_load_content_from_correct_version() - { - var eventData1 = new EventData(); - var eventData2 = new EventData(); - var eventData3 = new EventData(); - - var event1 = new ContentCreated { Data = new NamedContentData(), AppId = new NamedId(appId, "my-app") }; - var event2 = new ContentUpdated { Data = new NamedContentData() }; - var event3 = new ContentUpdated { Data = new NamedContentData() }; - - var events = new List - { - new StoredEvent("0", 0, eventData1), - new StoredEvent("1", 1, eventData2), - new StoredEvent("2", 2, eventData3) - }; - - A.CallTo(() => eventStore.GetEventsAsync(streamName, 0)) - .Returns(events); - - A.CallTo(() => formatter.Parse(eventData1, true)) - .Returns(new Envelope(event1)); - A.CallTo(() => formatter.Parse(eventData2, true)) - .Returns(new Envelope(event2)); - A.CallTo(() => formatter.Parse(eventData3, true)) - .Returns(new Envelope(event3)); - - var data = await sut.LoadAsync(appId, id, 1); - - Assert.Equal(event2.Data, data); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj b/tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj deleted file mode 100644 index eb908d328..000000000 --- a/tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - Exe - netcoreapp2.0 - Squidex.Domain.Apps.Write - - - - - - - - - - - - - - - - - - - - - - ..\..\Squidex.ruleset - - diff --git a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj index 743ad2bf9..957bb6b03 100644 --- a/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj +++ b/tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs index ea6bf1899..805d24005 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/AggregateHandlerTests.cs @@ -7,69 +7,90 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; -using Squidex.Infrastructure.Commands.TestHelpers; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.Commands { public class AggregateHandlerTests { + private readonly ISemanticLog log = A.Fake(); private readonly IServiceProvider serviceProvider = A.Fake(); - private readonly IStore store = A.Fake(); + private readonly IStore store = A.Fake>(); private readonly IStateFactory stateFactory = A.Fake(); - private readonly IPersistence persistence = A.Fake>(); + private readonly IPersistence persistence = A.Fake>(); private readonly Envelope event1 = new Envelope(new MyEvent()); private readonly Envelope event2 = new Envelope(new MyEvent()); - private readonly DomainObjectFactoryFunction factory; private readonly CommandContext context; - private readonly AggregateHandler sut; - private readonly DomainObjectWrapper domainObjectWrapper = new DomainObjectWrapper(); + private readonly CommandContext invalidContext = new CommandContext(A.Dummy()); private readonly Guid domainObjectId = Guid.NewGuid(); - private readonly MyDomainObject domainObject; + private readonly MyCommand command; + private readonly MyDomainObject domainObject = new MyDomainObject(); + private readonly AggregateHandler sut; public AggregateHandlerTests() { - factory = new DomainObjectFactoryFunction(id => domainObject); + command = new MyCommand { AggregateId = domainObjectId, ExpectedVersion = EtagVersion.Any }; + context = new CommandContext(command); - domainObject = - new MyDomainObject(domainObjectId, 1) - .RaiseNewEvent(event1) - .RaiseNewEvent(event2); + A.CallTo(() => store.WithSnapshots(domainObjectId, A>.Ignored)) + .Returns(persistence); - context = new CommandContext(new MyCommand { AggregateId = domainObject.Id }); + A.CallTo(() => stateFactory.CreateAsync(domainObjectId)) + .Returns(Task.FromResult(domainObject)); - A.CallTo(() => store.WithEventSourcing(domainObjectId.ToString(), A, Task>>.Ignored)) - .Returns(persistence); + A.CallTo(() => stateFactory.GetSingleAsync(domainObjectId)) + .Returns(Task.FromResult(domainObject)); - A.CallTo(() => serviceProvider.GetService(factory.GetType())) - .Returns(factory); + sut = new AggregateHandler(stateFactory, serviceProvider, log); - A.CallTo(() => stateFactory.GetDetachedAsync>(domainObject.Id.ToString())) - .Returns(Task.FromResult(domainObjectWrapper)); + domainObject.ActivateAsync(domainObjectId, store).Wait(); + } - sut = new AggregateHandler(stateFactory, serviceProvider); + [Fact] + public Task Create_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.CreateAsync(invalidContext, x => TaskHelper.False)); + } - domainObjectWrapper.ActivateAsync(domainObjectId.ToString(), store).Wait(); + [Fact] + public Task Create_synced_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.CreateSyncedAsync(invalidContext, x => TaskHelper.False)); } [Fact] - public Task Create_async_should_throw_exception_if_not_aggregate_command() + public Task Create_with_task_should_should_throw_exception_if_version_is_wrong() { - return Assert.ThrowsAnyAsync(() => sut.CreateAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); + command.ExpectedVersion = 2; + + return Assert.ThrowsAnyAsync(() => sut.CreateAsync(context, x => TaskHelper.False)); } [Fact] - public async Task Create_async_should_create_domain_object_and_save() + public Task Create_synced_with_task_should_should_throw_exception_if_version_is_wrong() + { + command.ExpectedVersion = 2; + + return Assert.ThrowsAnyAsync(() => sut.CreateSyncedAsync(context, x => TaskHelper.False)); + } + + [Fact] + public async Task Create_with_task_should_create_domain_object_and_save() { MyDomainObject passedDomainObject = null; await sut.CreateAsync(context, async x => { + x.RaiseEvent(new MyEvent()); + await Task.Yield(); passedDomainObject = x; @@ -78,78 +99,188 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(domainObject, passedDomainObject); Assert.NotNull(context.Result>()); - A.CallTo(() => persistence.ReadAsync(-1)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); + } - A.CallTo(() => persistence.WriteEventsAsync(A[]>.Ignored)) + [Fact] + public async Task Create_synced_with_task_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.CreateSyncedAsync(context, async x => + { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + + await Task.Yield(); + + passedDomainObject = x; + }); + + Assert.Equal(1, domainObject.State.Version); + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result>()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); } [Fact] - public async Task Create_sync_should_create_domain_object_and_save() + public async Task Create_should_create_domain_object_and_save() { MyDomainObject passedDomainObject = null; await sut.CreateAsync(context, x => { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + passedDomainObject = x; }); + Assert.Equal(1, domainObject.State.Version); Assert.Equal(domainObject, passedDomainObject); Assert.NotNull(context.Result>()); - A.CallTo(() => persistence.ReadAsync(-1)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); + } + + [Fact] + public async Task Create_synced_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.CreateSyncedAsync(context, x => + { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + + passedDomainObject = x; + }); + + Assert.Equal(1, domainObject.State.Version); + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result>()); - A.CallTo(() => persistence.WriteEventsAsync(A[]>.Ignored)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); } [Fact] - public Task Update_async_should_throw_exception_if_not_aggregate_command() + public Task Update_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.UpdateAsync(invalidContext, x => TaskHelper.False)); + } + + [Fact] + public Task Update_synced_with_task_should_throw_exception_if_not_aggregate_command() + { + return Assert.ThrowsAnyAsync(() => sut.UpdateSyncedAsync(invalidContext, x => TaskHelper.False)); + } + + [Fact] + public Task Update_with_task_should_should_throw_exception_if_version_is_wrong() { - return Assert.ThrowsAnyAsync(() => sut.UpdateAsync(new CommandContext(A.Dummy()), x => TaskHelper.False)); + command.ExpectedVersion = 2; + + return Assert.ThrowsAnyAsync(() => sut.UpdateAsync(context, x => TaskHelper.False)); } [Fact] - public async Task Update_async_should_create_domain_object_and_save() + public Task Update_synced_with_task_should_should_throw_exception_if_version_is_wrong() + { + command.ExpectedVersion = 2; + + return Assert.ThrowsAnyAsync(() => sut.UpdateSyncedAsync(context, x => TaskHelper.False)); + } + + [Fact] + public async Task Update_with_task_should_create_domain_object_and_save() { MyDomainObject passedDomainObject = null; await sut.UpdateAsync(context, async x => { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + await Task.Yield(); passedDomainObject = x; }); + Assert.Equal(1, domainObject.State.Version); Assert.Equal(domainObject, passedDomainObject); Assert.NotNull(context.Result()); - A.CallTo(() => persistence.ReadAsync(null)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); + } + + [Fact] + public async Task Update_synced_with_task_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.UpdateSyncedAsync(context, async x => + { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); - A.CallTo(() => persistence.WriteEventsAsync(A[]>.Ignored)) + await Task.Yield(); + + passedDomainObject = x; + }); + + Assert.Equal(1, domainObject.State.Version); + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result()); + + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); } [Fact] - public async Task Update_sync_should_create_domain_object_and_save() + public async Task Update_should_create_domain_object_and_save() { MyDomainObject passedDomainObject = null; await sut.UpdateAsync(context, x => { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + passedDomainObject = x; }); + Assert.Equal(1, domainObject.State.Version); Assert.Equal(domainObject, passedDomainObject); Assert.NotNull(context.Result()); - A.CallTo(() => persistence.ReadAsync(null)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); + } + + [Fact] + public async Task Update_synced_should_create_domain_object_and_save() + { + MyDomainObject passedDomainObject = null; + + await sut.UpdateSyncedAsync(context, x => + { + x.RaiseEvent(new MyEvent()); + x.RaiseEvent(new MyEvent()); + + passedDomainObject = x; + }); + + Assert.Equal(1, domainObject.State.Version); + Assert.Equal(domainObject, passedDomainObject); + Assert.NotNull(context.Result()); - A.CallTo(() => persistence.WriteEventsAsync(A[]>.Ignored)) + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) .MustHaveHappened(); } } diff --git a/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs index 8e7256bdb..e8d043fcc 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/CommandContextTests.cs @@ -7,7 +7,7 @@ // ========================================================================== using System; -using Squidex.Infrastructure.Commands.TestHelpers; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.Commands diff --git a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs index 8db73aea9..9d6478232 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectBaseTests.cs @@ -7,107 +7,101 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Linq; -using Squidex.Infrastructure.Commands.TestHelpers; +using System.Threading.Tasks; +using FakeItEasy; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Log; +using Squidex.Infrastructure.States; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.Commands { public class DomainObjectBaseTests { + private readonly IStore store = A.Fake>(); + private readonly IPersistence persistence = A.Fake>(); + private readonly Guid id = Guid.NewGuid(); + private readonly MyDomainObject sut = new MyDomainObject(); + + public DomainObjectBaseTests() + { + A.CallTo(() => store.WithSnapshots(id, A>.Ignored)) + .Returns(persistence); + } + [Fact] public void Should_instantiate() { - var domainObjectId = Guid.NewGuid(); - var domainObjectVersion = 123; - - var sut = new MyDomainObject(domainObjectId, domainObjectVersion); - - Assert.Equal(domainObjectId, sut.Id); - Assert.Equal(domainObjectVersion, sut.Version); + Assert.Equal(EtagVersion.Empty, sut.Version); } [Fact] - public void Should_add_event_to_uncommitted_events_and_increase_version_when_raised() + public void Should_add_event_to_uncommitted_events_and_not_increase_version_when_raised() { var event1 = new MyEvent(); var event2 = new MyEvent(); - var sut = new MyDomainObject(Guid.NewGuid(), 10); + sut.RaiseEvent(event1); + sut.RaiseEvent(event2); - IAggregate aggregate = sut; + Assert.Equal(EtagVersion.Empty, sut.Version); + Assert.Equal(new IEvent[] { event1, event2 }, sut.GetUncomittedEvents().Select(x => x.Payload).ToArray()); - sut.RaiseNewEvent(event1); - sut.RaiseNewEvent(event2); - - Assert.Equal(12, sut.Version); - - Assert.Equal(new IEvent[] { event1, event2 }, aggregate.GetUncomittedEvents().Select(x => x.Payload).ToArray()); - - aggregate.ClearUncommittedEvents(); + sut.ClearUncommittedEvents(); Assert.Equal(0, sut.GetUncomittedEvents().Count); } [Fact] - public void Should_not_add_event_to_uncommitted_events_and_increase_version_when_raised() + public async Task Should_write_state_and_events_when_saved() { + await sut.ActivateAsync(id, store); + var event1 = new MyEvent(); var event2 = new MyEvent(); + var newState = new MyDomainState(); - var sut = new MyDomainObject(Guid.NewGuid(), 10); - - IAggregate aggregate = sut; + sut.RaiseEvent(event1); + sut.RaiseEvent(event2); + sut.UpdateState(newState); - aggregate.ApplyEvent(new Envelope(event1)); - aggregate.ApplyEvent(new Envelope(event2)); + await sut.WriteAsync(A.Fake()); - Assert.Equal(12, sut.Version); - Assert.Equal(0, sut.GetUncomittedEvents().Count); - } - - [Fact] - public void Should_make_correct_equal_comparisons() - { - var id1 = Guid.NewGuid(); - var id2 = Guid.NewGuid(); - - var user1a = new MyDomainObject(id1, 1); - var user1b = new MyDomainObject(id1, 2); - var user2a = new MyDomainObject(id2, 2); + A.CallTo(() => persistence.WriteSnapshotAsync(newState)) + .MustHaveHappened(); + A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 2))) + .MustHaveHappened(); - Assert.True(user1a.Equals(user1b)); - Assert.False(user1a.Equals(user2a)); + Assert.Empty(sut.GetUncomittedEvents()); } [Fact] - public void Should_make_correct_object_equal_comparisons() + public async Task Should_ignore_exception_when_saving() { - var id1 = Guid.NewGuid(); - var id2 = Guid.NewGuid(); + A.CallTo(() => persistence.WriteEventsAsync(A>>.Ignored)) + .Throws(new InvalidOperationException()); - var user1a = new MyDomainObject(id1, 1); + await sut.ActivateAsync(id, store); - object user1b = new MyDomainObject(id1, 2); - object user2a = new MyDomainObject(id2, 2); + var event1 = new MyEvent(); + var event2 = new MyEvent(); + var newState = new MyDomainState(); - Assert.True(user1a.Equals(user1b)); - Assert.False(user1a.Equals(user2a)); - } + sut.RaiseEvent(event1); + sut.RaiseEvent(event2); + sut.UpdateState(newState); - [Fact] - public void Should_provide_correct_hash_codes() - { - var id1 = Guid.NewGuid(); - var id2 = Guid.NewGuid(); + await sut.WriteAsync(A.Fake()); - var user1a = new MyDomainObject(id1, 1); - var user1b = new MyDomainObject(id1, 2); - var user2a = new MyDomainObject(id2, 2); + A.CallTo(() => persistence.WriteSnapshotAsync(newState)) + .MustHaveHappened(); + A.CallTo(() => persistence.WriteEventsAsync(A>>.That.Matches(x => x.Count() == 2))) + .MustHaveHappened(); - Assert.Equal(user1a.GetHashCode(), user1b.GetHashCode()); - Assert.NotEqual(user1a.GetHashCode(), user2a.GetHashCode()); + Assert.Empty(sut.GetUncomittedEvents()); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs b/tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs index 01f0dda59..febba61c0 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Commands/EnrichWithTimestampCommandMiddlewareTests.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using FakeItEasy; using NodaTime; -using Squidex.Infrastructure.Commands.TestHelpers; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.Commands diff --git a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyDomainObject.cs b/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyDomainObject.cs deleted file mode 100644 index 33d65efd8..000000000 --- a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyDomainObject.cs +++ /dev/null @@ -1,39 +0,0 @@ -// ========================================================================== -// AggregateHandlerTests.cs -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex Group -// All rights reserved. -// ========================================================================== - -using System; -using Squidex.Infrastructure.EventSourcing; - -namespace Squidex.Infrastructure.Commands.TestHelpers -{ - internal sealed class MyDomainObject : DomainObjectBase - { - public MyDomainObject(Guid id, int version) - : base(id, version) - { - } - - public MyDomainObject RaiseNewEvent(IEvent @event) - { - RaiseEvent(@event); - - return this; - } - - public MyDomainObject RaiseNewEvent(Envelope @event) - { - RaiseEvent(@event); - - return this; - } - - protected override void DispatchEvent(Envelope @event) - { - } - } -} diff --git a/tests/Squidex.Infrastructure.Tests/DispatchingTests.cs b/tests/Squidex.Infrastructure.Tests/DispatchingTests.cs index 0eee5acf1..bd8a5a8b4 100644 --- a/tests/Squidex.Infrastructure.Tests/DispatchingTests.cs +++ b/tests/Squidex.Infrastructure.Tests/DispatchingTests.cs @@ -208,7 +208,7 @@ namespace Squidex.Infrastructure } [Fact] - public async Task Should_invoke_correct_event_asynchronously() + public async Task Should_invoke_correct_event_with_taskhronously() { var consumer = new MyAsyncConsumer(); @@ -222,7 +222,7 @@ namespace Squidex.Infrastructure } [Fact] - public async Task Should_invoke_correct_event_with_context_asynchronously() + public async Task Should_invoke_correct_event_with_context_with_taskhronously() { var consumer = new MyAsyncConsumer(); @@ -264,7 +264,7 @@ namespace Squidex.Infrastructure } [Fact] - public async Task Should_invoke_correct_event_and_return_synchronously() + public async Task Should_invoke_correct_event_and_returnhronously() { var consumer = new MyAsyncFuncConsumer(); @@ -278,7 +278,7 @@ namespace Squidex.Infrastructure } [Fact] - public async Task Should_invoke_correct_event_with_context_and_return_synchronously() + public async Task Should_invoke_correct_event_with_context_and_returnhronously() { var consumer = new MyAsyncFuncConsumer(); diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs index 817867902..39927fbca 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/CompoundEventConsumerTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using FakeItEasy; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.EventSourcing @@ -18,10 +19,6 @@ namespace Squidex.Infrastructure.EventSourcing private readonly IEventConsumer consumer1 = A.Fake(); private readonly IEventConsumer consumer2 = A.Fake(); - private sealed class MyEvent : IEvent - { - } - [Fact] public void Should_return_given_name() { diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs index 9c5bb6101..6670ee657 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs @@ -81,7 +81,18 @@ namespace Squidex.Infrastructure.EventSourcing sut.SetEventStreamNumber(eventStreamNumber); Assert.Equal(eventStreamNumber, sut.Headers.EventStreamNumber()); - Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt32(culture)); + Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt64(culture)); + } + + [Fact] + public void Should_set_and_get_snapshot_version() + { + const int snapshotVersion = 123; + + sut.SetSnapshotVersion(snapshotVersion); + + Assert.Equal(snapshotVersion, sut.Headers.SnapshotVersion()); + Assert.Equal(snapshotVersion, sut.Headers["SnapshotVersion"].ToInt64(culture)); } } } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs index 6c0261cf5..82f6b0946 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/EventDataFormatterTests.cs @@ -11,17 +11,13 @@ using System.Linq; using Newtonsoft.Json; using NodaTime; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.EventSourcing { public class EventDataFormatterTests { - public sealed class MyEvent : IEvent - { - public string MyProperty { get; set; } - } - public sealed class MyOldEvent : IEvent, IMigratedEvent { public string MyProperty { get; set; } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs index b5537df60..55a908e72 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs @@ -12,16 +12,13 @@ using FakeItEasy; using FluentAssertions; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.States; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.EventSourcing.Grains { public class EventConsumerGrainTests { - public sealed class MyEvent : IEvent - { - } - public sealed class MyEventConsumerGrain : EventConsumerGrain { public MyEventConsumerGrain(IEventStore eventStore, IEventDataFormatter eventDataFormatter, ISemanticLog log) @@ -41,7 +38,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains private readonly IEventSubscription eventSubscription = A.Fake(); private readonly IPersistence persistence = A.Fake>(); private readonly ISemanticLog log = A.Fake(); - private readonly IStore store = A.Fake(); + private readonly IStore store = A.Fake>(); private readonly IEventDataFormatter formatter = A.Fake(); private readonly EventData eventData = new EventData(); private readonly Envelope envelope = new Envelope(new MyEvent()); @@ -57,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains consumerName = eventConsumer.GetType().Name; - A.CallTo(() => store.WithSnapshots(consumerName, A>.Ignored)) + A.CallTo(() => store.WithSnapshots(consumerName, A>.Ignored)) .Invokes(new Action>((key, a) => apply = a)) .Returns(persistence); @@ -67,8 +64,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains A.CallTo(() => eventConsumer.Name) .Returns(consumerName); - A.CallTo(() => persistence.ReadAsync(null)) - .Invokes(new Action(s => apply(state))); + A.CallTo(() => persistence.ReadAsync(EtagVersion.Any)) + .Invokes(new Action(s => apply(state))); A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .Invokes(new Action(s => state = s)); @@ -229,6 +226,27 @@ namespace Squidex.Infrastructure.EventSourcing.Grains .MustNotHaveHappened(); } + [Fact] + public async Task Should_stop_if_consumer_failed() + { + sut.ActivateAsync(consumerName, store).Wait(); + sut.Activate(eventConsumer); + + var ex = new InvalidOperationException(); + + await OnErrorAsync(eventSubscription, ex); + + sut.Dispose(); + + state.ShouldBeEquivalentTo(new EventConsumerState { IsStopped = true, Position = initialPosition, Error = ex.ToString() }); + + A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) + .MustHaveHappened(Repeated.Exactly.Once); + + A.CallTo(() => eventSubscription.StopAsync()) + .MustHaveHappened(Repeated.Exactly.Once); + } + [Fact] public async Task Should_not_make_error_handling_when_exception_is_from_another_subscription() { diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerManagerTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerManagerTests.cs index db9602b0c..e6d1edd31 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerManagerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerManagerTests.cs @@ -33,8 +33,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains A.CallTo(() => consumer1.Name).Returns(consumerName1); A.CallTo(() => consumer2.Name).Returns(consumerName2); - A.CallTo(() => factory.GetDetachedAsync(consumerName1)).Returns(actor1); - A.CallTo(() => factory.GetDetachedAsync(consumerName2)).Returns(actor2); + A.CallTo(() => factory.CreateAsync(consumerName1)).Returns(actor1); + A.CallTo(() => factory.CreateAsync(consumerName2)).Returns(actor2); sut = new EventConsumerGrainManager(new IEventConsumer[] { consumer1, consumer2 }, pubSub, factory); } diff --git a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs index c4b900f36..98e95a985 100644 --- a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs +++ b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs @@ -96,6 +96,18 @@ namespace Squidex.Infrastructure Assert.Equal(language, Language.GetLanguage(languageCode)); } + [Theory] + [InlineData("en-US", "en")] + [InlineData("en-GB", "en")] + [InlineData("EN-US", "en")] + [InlineData("EN-GB", "en")] + public void Should_parse_lanuages_from_culture(string input, string languageCode) + { + var language = Language.ParseOrNull(input); + + Assert.Equal(language, Language.GetLanguage(languageCode)); + } + [Theory] [InlineData("")] [InlineData(" ")] diff --git a/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs b/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs new file mode 100644 index 000000000..adf71d3a5 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Migrations/MigratorTests.cs @@ -0,0 +1,111 @@ +// ========================================================================== +// MigratorTests.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using FakeItEasy; +using Squidex.Infrastructure.Log; +using Xunit; + +namespace Squidex.Infrastructure.Migrations +{ + public sealed class MigratorTests + { + private readonly IMigrationStatus status = A.Fake(); + + public MigratorTests() + { + A.CallTo(() => status.GetVersionAsync()).Returns(0); + A.CallTo(() => status.TryLockAsync()).Returns(true); + } + + [Fact] + public async Task Should_migrate_step_by_step() + { + var migrator_0_1 = BuildMigration(0, 1); + var migrator_1_2 = BuildMigration(1, 2); + var migrator_2_3 = BuildMigration(2, 3); + + var migrator = new Migrator(status, new[] { migrator_0_1, migrator_1_2, migrator_2_3 }, A.Fake()); + + await migrator.MigrateAsync(); + + A.CallTo(() => migrator_0_1.UpdateAsync()).MustHaveHappened(); + A.CallTo(() => migrator_1_2.UpdateAsync()).MustHaveHappened(); + A.CallTo(() => migrator_2_3.UpdateAsync()).MustHaveHappened(); + + A.CallTo(() => status.UnlockAsync(3)).MustHaveHappened(); + } + + [Fact] + public async Task Should_unlock_when_failed() + { + var migrator_0_1 = BuildMigration(0, 1); + var migrator_1_2 = BuildMigration(1, 2); + var migrator_2_3 = BuildMigration(2, 3); + + var migrator = new Migrator(status, new[] { migrator_0_1, migrator_1_2, migrator_2_3 }, A.Fake()); + + A.CallTo(() => migrator_1_2.UpdateAsync()).Throws(new ArgumentException()); + + await Assert.ThrowsAsync(migrator.MigrateAsync); + + A.CallTo(() => migrator_0_1.UpdateAsync()).MustHaveHappened(); + A.CallTo(() => migrator_1_2.UpdateAsync()).MustHaveHappened(); + A.CallTo(() => migrator_2_3.UpdateAsync()).MustNotHaveHappened(); + + A.CallTo(() => status.UnlockAsync(1)).MustHaveHappened(); + } + + [Fact] + public async Task Should_migrate_with_fastest_path() + { + var migrator_0_1 = BuildMigration(0, 1); + var migrator_0_2 = BuildMigration(0, 2); + var migrator_1_2 = BuildMigration(1, 2); + var migrator_2_3 = BuildMigration(2, 3); + + var migrator = new Migrator(status, new[] { migrator_0_1, migrator_0_2, migrator_1_2, migrator_2_3 }, A.Fake()); + + await migrator.MigrateAsync(); + + A.CallTo(() => migrator_0_2.UpdateAsync()).MustHaveHappened(); + A.CallTo(() => migrator_0_1.UpdateAsync()).MustNotHaveHappened(); + A.CallTo(() => migrator_1_2.UpdateAsync()).MustNotHaveHappened(); + A.CallTo(() => migrator_2_3.UpdateAsync()).MustHaveHappened(); + + A.CallTo(() => status.UnlockAsync(3)).MustHaveHappened(); + } + + [Fact] + public async Task Should_throw_if_no_path_found() + { + var migrator_0_1 = BuildMigration(0, 1); + var migrator_2_3 = BuildMigration(2, 3); + + var migrator = new Migrator(status, new[] { migrator_0_1, migrator_2_3 }, A.Fake()); + + await Assert.ThrowsAsync(migrator.MigrateAsync); + + A.CallTo(() => migrator_0_1.UpdateAsync()).MustNotHaveHappened(); + A.CallTo(() => migrator_2_3.UpdateAsync()).MustNotHaveHappened(); + + A.CallTo(() => status.UnlockAsync(0)).MustHaveHappened(); + } + + private IMigration BuildMigration(int fromVersion, int toVersion) + { + var migration = A.Fake(); + + A.CallTo(() => migration.FromVersion).Returns(fromVersion); + A.CallTo(() => migration.ToVersion).Returns(toVersion); + + return migration; + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs b/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs index 4ebdd6038..a833217e5 100644 --- a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs +++ b/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs @@ -86,7 +86,7 @@ namespace Squidex.Infrastructure Assert.True(bag.Contains("NewKey")); Assert.Equal(1, bag.Count); - Assert.Equal(123, bag["NewKey"].ToInt32(c)); + Assert.Equal(123, bag["NewKey"].ToInt64(c)); Assert.False(bag.Contains("OldKey")); } @@ -174,7 +174,7 @@ namespace Squidex.Infrastructure { bag.Set("Key", "abc"); - Assert.Throws(() => bag["Key"].ToInt32(CultureInfo.InvariantCulture)); + Assert.Throws(() => bag["Key"].ToInt64(CultureInfo.InvariantCulture)); } [Fact] @@ -347,7 +347,7 @@ namespace Squidex.Infrastructure private void AssertNumber() { - AssertInt32(123); + AssertInt64(123); AssertInt64(123); AssertSingle(123); AssertDouble(123); @@ -420,10 +420,10 @@ namespace Squidex.Infrastructure Assert.Equal(expected, (long?)dynamicBag.Key); } - private void AssertInt32(int expected) + private void AssertInt64(int expected) { - Assert.Equal(expected, bag["Key"].ToInt32(c)); - Assert.Equal(expected, bag["Key"].ToNullableInt32(c)); + Assert.Equal(expected, bag["Key"].ToInt64(c)); + Assert.Equal(expected, bag["Key"].ToNullableInt64(c)); Assert.Equal(expected, (int)dynamicBag.Key); Assert.Equal(expected, (int?)dynamicBag.Key); diff --git a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs index f6a59a82c..ba19dd2c7 100644 --- a/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Reflection/SimpleCopierTests.cs @@ -37,13 +37,24 @@ namespace Squidex.Infrastructure.Reflection { public int Value2 { get; set; } + public int ValueReadOnly { get; } + public Cloneable Cloneable { get; set; } + + public MyClass1() + { + } + + public MyClass1(int readValue) + { + ValueReadOnly = readValue; + } } [Fact] public void Should_copy_class() { - var value = new MyClass1 + var value = new MyClass1(100) { Value1 = 1, Value2 = 2, @@ -55,6 +66,8 @@ namespace Squidex.Infrastructure.Reflection Assert.Equal(value.Value1, copy.Value1); Assert.Equal(value.Value2, copy.Value2); + Assert.Equal(0, copy.ValueReadOnly); + Assert.Equal(value.Cloneable.Value, copy.Cloneable.Value); Assert.NotSame(value.Cloneable, copy.Cloneable); } diff --git a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj index 50a236ab7..87a70e941 100644 --- a/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj +++ b/tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs b/tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs index 76198d0b1..ac1581d2e 100644 --- a/tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs +++ b/tests/Squidex.Infrastructure.Tests/States/StateEventSourcingTests.cs @@ -15,31 +15,28 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Tasks; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.States { public class StateEventSourcingTests { - public sealed class MyEvent : IEvent - { - } - - private class MyStatefulObject : IStatefulObject + private class MyStatefulObject : IStatefulObject { private readonly List appliedEvents = new List(); - private IPersistence persistence; + private IPersistence persistence; - public long? ExpectedVersion { get; set; } + public long ExpectedVersion { get; set; } = EtagVersion.Any; public List AppliedEvents { get { return appliedEvents; } } - public Task ActivateAsync(string key, IStore store) + public Task ActivateAsync(string key, IStore store) { - persistence = store.WithEventSourcing(key, e => appliedEvents.Add(e.Payload)); + persistence = store.WithEventSourcing(key, e => appliedEvents.Add(e.Payload)); return persistence.ReadAsync(ExpectedVersion); } @@ -50,15 +47,15 @@ namespace Squidex.Infrastructure.States } } - private class MyStatefulObjectWithSnapshot : IStatefulObject + private class MyStatefulObjectWithSnapshot : IStatefulObject { - private IPersistence persistence; + private IPersistence persistence; - public long? ExpectedVersion { get; set; } + public long ExpectedVersion { get; set; } = EtagVersion.Any; - public Task ActivateAsync(string key, IStore store) + public Task ActivateAsync(string key, IStore store) { - persistence = store.WithSnapshotsAndEventSourcing(key, s => TaskHelper.Done, s => TaskHelper.Done); + persistence = store.WithSnapshotsAndEventSourcing(key, s => TaskHelper.Done, s => TaskHelper.Done); return persistence.ReadAsync(ExpectedVersion); } @@ -72,7 +69,7 @@ namespace Squidex.Infrastructure.States private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly IPubSub pubSub = new InMemoryPubSub(true); private readonly IServiceProvider services = A.Fake(); - private readonly ISnapshotStore snapshotStore = A.Fake(); + private readonly ISnapshotStore snapshotStore = A.Fake>(); private readonly IStreamNameResolver streamNameResolver = A.Fake(); private readonly StateFactory sut; @@ -82,13 +79,15 @@ namespace Squidex.Infrastructure.States .Returns(statefulObject); A.CallTo(() => services.GetService(typeof(MyStatefulObjectWithSnapshot))) .Returns(statefulObjectWithSnapShot); + A.CallTo(() => services.GetService(typeof(ISnapshotStore))) + .Returns(snapshotStore); A.CallTo(() => streamNameResolver.GetStreamName(typeof(MyStatefulObject), key)) .Returns(key); A.CallTo(() => streamNameResolver.GetStreamName(typeof(MyStatefulObjectWithSnapshot), key)) .Returns(key); - sut = new StateFactory(pubSub, cache, eventStore, eventDataFormatter, services, snapshotStore, streamNameResolver); + sut = new StateFactory(pubSub, cache, eventStore, eventDataFormatter, services, streamNameResolver); sut.Connect(); } @@ -102,7 +101,7 @@ namespace Squidex.Infrastructure.States SetupEventStore(event1, event2); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.NotNull(cache.Get(key)); @@ -111,16 +110,14 @@ namespace Squidex.Infrastructure.States } [Fact] - public async Task Should_read_events_from_snapshot() + public async Task Should_read_status_from_snapshot() { - statefulObjectWithSnapShot.ExpectedVersion = null; - - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .Returns((2, 2L)); SetupEventStore(3, 2); - await sut.GetSynchronizedAsync(key); + await sut.GetSingleAsync(key); A.CallTo(() => eventStore.GetEventsAsync(key, 3)) .MustHaveHappened(); @@ -129,27 +126,23 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_throw_exception_if_events_are_older_than_snapshot() { - statefulObjectWithSnapShot.ExpectedVersion = null; - - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .Returns((2, 2L)); SetupEventStore(3, 0, 3); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] public async Task Should_throw_exception_if_events_have_gaps_to_snapshot() { - statefulObjectWithSnapShot.ExpectedVersion = null; - - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .Returns((2, 2L)); SetupEventStore(3, 4, 3); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] @@ -159,7 +152,7 @@ namespace Squidex.Infrastructure.States SetupEventStore(0); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] @@ -169,27 +162,40 @@ namespace Squidex.Infrastructure.States SetupEventStore(3); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); + } + + [Fact] + public async Task Should_throw_exception_if_other_version_found_from_snapshot() + { + statefulObjectWithSnapShot.ExpectedVersion = 1; + + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((2, 2L)); + + SetupEventStore(0); + + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] public async Task Should_not_throw_exception_if_noting_expected() { - statefulObject.ExpectedVersion = null; + statefulObject.ExpectedVersion = EtagVersion.Any; SetupEventStore(0); - await sut.GetSynchronizedAsync(key); + await sut.GetSingleAsync(key); } [Fact] public async Task Should_provide_state_from_services_and_add_to_cache() { - statefulObject.ExpectedVersion = null; + statefulObject.ExpectedVersion = EtagVersion.Any; SetupEventStore(0); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.NotNull(cache.Get(key)); @@ -198,16 +204,14 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_serve_next_request_from_cache() { - statefulObject.ExpectedVersion = null; - SetupEventStore(0); - var actualObject1 = await sut.GetSynchronizedAsync(key); + var actualObject1 = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject1); Assert.NotNull(cache.Get(key)); - var actualObject2 = await sut.GetSynchronizedAsync(key); + var actualObject2 = await sut.GetSingleAsync(key); A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .MustHaveHappened(Repeated.Exactly.Once); @@ -216,8 +220,6 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_write_to_store_with_previous_position() { - statefulObject.ExpectedVersion = null; - InvalidateMessage message = null; pubSub.Subscribe(m => @@ -227,7 +229,7 @@ namespace Squidex.Infrastructure.States SetupEventStore(3); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); @@ -246,11 +248,9 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_wrap_exception_when_writing_to_store_with_previous_position() { - statefulObject.ExpectedVersion = null; - SetupEventStore(3); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); A.CallTo(() => eventStore.AppendEventsAsync(A.Ignored, key, 2, A>.That.Matches(x => x.Count == 2))) .Throws(new WrongEventVersionException(1, 1)); @@ -261,9 +261,7 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_remove_from_cache_when_invalidation_message_received() { - statefulObject.ExpectedVersion = null; - - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); await InvalidateCacheAsync(); @@ -271,18 +269,29 @@ namespace Squidex.Infrastructure.States } [Fact] - public async Task Should_return_same_instance_for_parallel_requests() + public async Task Should_remove_from_cache_when_write_failed() { - statefulObject.ExpectedVersion = null; + A.CallTo(() => eventStore.AppendEventsAsync(A.Ignored, A.Ignored, A.Ignored, A>.Ignored)) + .Throws(new InvalidOperationException()); + + var actualObject = await sut.GetSingleAsync(key); - A.CallTo(() => snapshotStore.ReadAsync(key)) - .ReturnsLazily(() => Task.Delay(1).ContinueWith(x => (1, 1L))); + await Assert.ThrowsAsync(() => statefulObject.WriteEventsAsync(new MyEvent())); + + Assert.False(cache.TryGetValue(key, out var t)); + } + + [Fact] + public async Task Should_return_same_instance_for_parallel_requests() + { + A.CallTo(() => snapshotStore.ReadAsync(key)) + .ReturnsLazily(() => Task.Delay(1).ContinueWith(x => ((object)1, 1L))); var tasks = new List>(); for (var i = 0; i < 1000; i++) { - tasks.Add(Task.Run(() => sut.GetSynchronizedAsync(key))); + tasks.Add(Task.Run(() => sut.GetSingleAsync(key))); } var retrievedStates = await Task.WhenAll(tasks); diff --git a/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs b/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs index a57f05ab1..1a84194a0 100644 --- a/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs +++ b/tests/Squidex.Infrastructure.Tests/States/StateSnapshotTests.cs @@ -15,25 +15,32 @@ using Microsoft.Extensions.Options; using Squidex.Infrastructure.EventSourcing; using Xunit; +#pragma warning disable RECS0002 // Convert anonymous method to method group + namespace Squidex.Infrastructure.States { public class StateSnapshotTests : IDisposable { - private class MyStatefulObject : IStatefulObject + private class MyStatefulObject : IStatefulObject { private IPersistence persistence; private int state; - public long? ExpectedVersion { get; set; } + public long ExpectedVersion { get; set; } = EtagVersion.Any; + + public long Version + { + get { return persistence.Version; } + } public int State { get { return state; } } - public Task ActivateAsync(string key, IStore store) + public Task ActivateAsync(string key, IStore store) { - persistence = store.WithSnapshots(key, s => state = s); + persistence = store.WithSnapshots(key, s => state = s); return persistence.ReadAsync(ExpectedVersion); } @@ -56,7 +63,7 @@ namespace Squidex.Infrastructure.States private readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); private readonly IPubSub pubSub = new InMemoryPubSub(true); private readonly IServiceProvider services = A.Fake(); - private readonly ISnapshotStore snapshotStore = A.Fake(); + private readonly ISnapshotStore snapshotStore = A.Fake>(); private readonly IStreamNameResolver streamNameResolver = A.Fake(); private readonly StateFactory sut; @@ -64,8 +71,10 @@ namespace Squidex.Infrastructure.States { A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .Returns(statefulObject); + A.CallTo(() => services.GetService(typeof(ISnapshotStore))) + .Returns(snapshotStore); - sut = new StateFactory(pubSub, cache, eventStore, eventDataFormatter, services, snapshotStore, streamNameResolver); + sut = new StateFactory(pubSub, cache, eventStore, eventDataFormatter, services, streamNameResolver); sut.Connect(); } @@ -79,10 +88,10 @@ namespace Squidex.Infrastructure.States { statefulObject.ExpectedVersion = 1; - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .Returns((123, 1)); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.NotNull(cache.Get(key)); @@ -90,15 +99,27 @@ namespace Squidex.Infrastructure.States Assert.Equal(123, statefulObject.State); } + [Fact] + public async Task Should_set_to_empty_when_store_returns_not_found() + { + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((123, EtagVersion.NotFound)); + + var actualObject = await sut.GetSingleAsync(key); + + Assert.Equal(-1, statefulObject.Version); + Assert.Equal( 0, statefulObject.State); + } + [Fact] public async Task Should_throw_exception_if_not_found() { statefulObject.ExpectedVersion = 0; - A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((0, -1)); + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((0, EtagVersion.Empty)); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] @@ -106,29 +127,25 @@ namespace Squidex.Infrastructure.States { statefulObject.ExpectedVersion = 1; - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .Returns((2, 2)); - await Assert.ThrowsAsync(() => sut.GetSynchronizedAsync(key)); + await Assert.ThrowsAsync(() => sut.GetSingleAsync(key)); } [Fact] public async Task Should_not_throw_exception_if_noting_expected() { - statefulObject.ExpectedVersion = null; - - A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((0, -1)); + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((0, EtagVersion.Empty)); - await sut.GetSynchronizedAsync(key); + await sut.GetSingleAsync(key); } [Fact] public async Task Should_provide_state_from_services_and_add_to_cache() { - statefulObject.ExpectedVersion = null; - - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.NotNull(cache.Get(key)); @@ -137,14 +154,12 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_serve_next_request_from_cache() { - statefulObject.ExpectedVersion = null; - - var actualObject1 = await sut.GetSynchronizedAsync(key); + var actualObject1 = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject1); Assert.NotNull(cache.Get(key)); - var actualObject2 = await sut.GetSynchronizedAsync(key); + var actualObject2 = await sut.GetSingleAsync(key); A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .MustHaveHappened(Repeated.Exactly.Once); @@ -153,14 +168,12 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_not_serve_next_request_from_cache_when_detached() { - statefulObject.ExpectedVersion = null; - - var actualObject1 = await sut.GetDetachedAsync(key); + var actualObject1 = await sut.CreateAsync(key); Assert.Same(statefulObject, actualObject1); Assert.Null(cache.Get(key)); - var actualObject2 = await sut.GetDetachedAsync(key); + var actualObject2 = await sut.CreateAsync(key); A.CallTo(() => services.GetService(typeof(MyStatefulObject))) .MustHaveHappened(Repeated.Exactly.Twice); @@ -169,10 +182,6 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_write_to_store_with_previous_version() { - statefulObject.ExpectedVersion = null; - - var version = 1; - InvalidateMessage message = null; pubSub.Subscribe(m => @@ -180,10 +189,10 @@ namespace Squidex.Infrastructure.States message = m; }); - A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((123, version)); + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((123, 13)); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); Assert.Same(statefulObject, actualObject); Assert.Equal(123, statefulObject.State); @@ -192,7 +201,7 @@ namespace Squidex.Infrastructure.States await statefulObject.WriteStateAsync(); - A.CallTo(() => snapshotStore.WriteAsync(key, 456, version, 2)) + A.CallTo(() => snapshotStore.WriteAsync(key, 456, 13, 14)) .MustHaveHappened(); Assert.NotNull(message); @@ -202,17 +211,13 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_wrap_exception_when_writing_to_store_with_previous_version() { - statefulObject.ExpectedVersion = null; - - var version = 1; - - A.CallTo(() => snapshotStore.ReadAsync(key)) - .Returns((123, version)); + A.CallTo(() => snapshotStore.ReadAsync(key)) + .Returns((123, 13)); - A.CallTo(() => snapshotStore.WriteAsync(key, 123, version, 2)) + A.CallTo(() => snapshotStore.WriteAsync(key, 123, 13, 14)) .Throws(new InconsistentStateException(1, 1, new InvalidOperationException())); - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); await Assert.ThrowsAsync(() => statefulObject.WriteStateAsync()); } @@ -220,9 +225,7 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_remove_from_cache_when_invalidation_message_received() { - statefulObject.ExpectedVersion = null; - - var actualObject = await sut.GetSynchronizedAsync(key); + var actualObject = await sut.GetSingleAsync(key); await InvalidateCacheAsync(); @@ -230,18 +233,29 @@ namespace Squidex.Infrastructure.States } [Fact] - public async Task Should_return_same_instance_for_parallel_requests() + public async Task Should_remove_from_cache_when_write_failed() { - statefulObject.ExpectedVersion = null; + A.CallTo(() => snapshotStore.WriteAsync(A.Ignored, A.Ignored, A.Ignored, A.Ignored)) + .Throws(new InvalidOperationException()); + + var actualObject = await sut.GetSingleAsync(key); - A.CallTo(() => snapshotStore.ReadAsync(key)) + await Assert.ThrowsAsync(() => statefulObject.WriteStateAsync()); + + Assert.False(cache.TryGetValue(key, out var t)); + } + + [Fact] + public async Task Should_return_same_instance_for_parallel_requests() + { + A.CallTo(() => snapshotStore.ReadAsync(key)) .ReturnsLazily(() => Task.Delay(1).ContinueWith(x => (1, 1L))); var tasks = new List>(); for (var i = 0; i < 1000; i++) { - tasks.Add(Task.Run(() => sut.GetSynchronizedAsync(key))); + tasks.Add(Task.Run(() => sut.GetSingleAsync(key))); } var retrievedStates = await Task.WhenAll(tasks); @@ -251,7 +265,7 @@ namespace Squidex.Infrastructure.States Assert.Same(retrievedStates[0], retrievedState); } - A.CallTo(() => snapshotStore.ReadAsync(key)) + A.CallTo(() => snapshotStore.ReadAsync(key)) .MustHaveHappened(Repeated.Exactly.Once); } diff --git a/tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs b/tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs index 5e110745c..25f0d5d7f 100644 --- a/tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs @@ -18,7 +18,7 @@ namespace Squidex.Infrastructure.Tasks private readonly SingleThreadedDispatcher sut = new SingleThreadedDispatcher(); [Fact] - public async Task Should_handle_async_messages_sequentially() + public async Task Should_handle_with_task_messages_sequentially() { var source = Enumerable.Range(1, 100); var target = new List(); @@ -39,7 +39,7 @@ namespace Squidex.Infrastructure.Tasks } [Fact] - public async Task Should_handle_sync_messages_sequentially() + public async Task Should_handle_messages_sequentially() { var source = Enumerable.Range(1, 100); var target = new List(); diff --git a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyCommand.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyCommand.cs similarity index 80% rename from tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyCommand.cs rename to tests/Squidex.Infrastructure.Tests/TestHelpers/MyCommand.cs index c610d9d08..6a5ee2a63 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyCommand.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyCommand.cs @@ -8,14 +8,15 @@ using System; using NodaTime; +using Squidex.Infrastructure.Commands; -namespace Squidex.Infrastructure.Commands.TestHelpers +namespace Squidex.Infrastructure.TestHelpers { internal sealed class MyCommand : IAggregateCommand, ITimestampCommand { public Guid AggregateId { get; set; } - public long? ExpectedVersion { get; set; } + public long ExpectedVersion { get; set; } public Instant Timestamp { get; set; } } diff --git a/src/Squidex.Domain.Apps.Read/Assets/IAssetEventConsumer.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs similarity index 64% rename from src/Squidex.Domain.Apps.Read/Assets/IAssetEventConsumer.cs rename to tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs index bd0a6ddb9..5ee760e7b 100644 --- a/src/Squidex.Domain.Apps.Read/Assets/IAssetEventConsumer.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainObject.cs @@ -1,16 +1,16 @@ // ========================================================================== -// IAssetEventConsumer.cs +// AggregateHandlerTests.cs // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex Group // All rights reserved. // ========================================================================== -using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Commands; -namespace Squidex.Domain.Apps.Read.Assets +namespace Squidex.Infrastructure.TestHelpers { - public interface IAssetEventConsumer : IEventConsumer + internal sealed class MyDomainObject : DomainObjectBase { } } diff --git a/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs new file mode 100644 index 000000000..ce0665b42 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyDomainState.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// MyDomainState.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using Squidex.Infrastructure.Commands; + +namespace Squidex.Infrastructure.TestHelpers +{ + public class MyDomainState : IDomainState + { + public long Version { get; set; } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyEvent.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyEvent.cs similarity index 82% rename from tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyEvent.cs rename to tests/Squidex.Infrastructure.Tests/TestHelpers/MyEvent.cs index e8d7e0f4b..cf2d048ce 100644 --- a/tests/Squidex.Infrastructure.Tests/Commands/TestHelpers/MyEvent.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/MyEvent.cs @@ -8,9 +8,10 @@ using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Infrastructure.Commands.TestHelpers +namespace Squidex.Infrastructure.TestHelpers { internal sealed class MyEvent : IEvent { + public string MyProperty { get; set; } } } \ No newline at end of file diff --git a/tools/Migrate_00/Migrate_00.csproj b/tools/Migrate_00/Migrate_00.csproj new file mode 100644 index 000000000..0720941f1 --- /dev/null +++ b/tools/Migrate_00/Migrate_00.csproj @@ -0,0 +1,14 @@ + + + Exe + netcoreapp2.0 + + + + + + + + ..\..\Squidex.ruleset + + diff --git a/tools/Migrate_01/Program.cs b/tools/Migrate_00/Program.cs similarity index 99% rename from tools/Migrate_01/Program.cs rename to tools/Migrate_00/Program.cs index 522763767..1429070b3 100644 --- a/tools/Migrate_01/Program.cs +++ b/tools/Migrate_00/Program.cs @@ -10,7 +10,7 @@ using System; using MongoDB.Bson; using MongoDB.Driver; -namespace Migrate_01 +namespace Migrate_00 { public class Program { diff --git a/tools/Migrate_01/Migrate_01.csproj b/tools/Migrate_01/Migrate_01.csproj index 0720941f1..c782b2dec 100644 --- a/tools/Migrate_01/Migrate_01.csproj +++ b/tools/Migrate_01/Migrate_01.csproj @@ -1,12 +1,12 @@ - + - Exe - netcoreapp2.0 + netstandard2.0 - - - + + + + ..\..\Squidex.ruleset diff --git a/tools/Migrate_01/Migration01.cs b/tools/Migrate_01/Migration01.cs new file mode 100644 index 000000000..c0a8c9e2e --- /dev/null +++ b/tools/Migrate_01/Migration01.cs @@ -0,0 +1,142 @@ +// ========================================================================== +// MigrateToEntities.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading; +using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Domain.Apps.Entities.Contents; +using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Assets; +using Squidex.Domain.Apps.Events.Contents; +using Squidex.Infrastructure; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.States; +using Squidex.Infrastructure.Tasks; + +namespace Migrate_01 +{ + public sealed class Migration01 : IMigration, IEventSubscriber + { + private readonly FieldRegistry fieldRegistry; + private readonly IEventStore eventStore; + private readonly IEventDataFormatter eventDataFormatter; + private readonly IStateFactory stateFactory; + private readonly Timer timer; + private readonly TaskCompletionSource subscriptionTcs = new TaskCompletionSource(); + + public int FromVersion { get; } = 0; + + public int ToVersion { get; } = 1; + + public Migration01( + FieldRegistry fieldRegistry, + IEventDataFormatter eventDataFormatter, + IEventStore eventStore, + IStateFactory stateFactory) + { + this.fieldRegistry = fieldRegistry; + this.eventDataFormatter = eventDataFormatter; + this.eventStore = eventStore; + this.stateFactory = stateFactory; + + timer = new Timer(d => subscriptionTcs.TrySetResult(true)); + } + + public async Task UpdateAsync() + { + var subscription = eventStore.CreateSubscription(this, ".*"); + + try + { + await subscriptionTcs.Task; + } + finally + { + await subscription.StopAsync(); + } + } + + public async Task OnEventAsync(IEventSubscription subscription, StoredEvent storedEvent) + { + try + { + timer.Change(Timeout.Infinite, Timeout.Infinite); + + var @event = ParseKnownEvent(storedEvent); + + if (@event != null) + { + var version = storedEvent.EventStreamNumber; + + if (@event.Payload is AssetEvent assetEvent) + { + var asset = await stateFactory.CreateAsync(assetEvent.AssetId); + + asset.UpdateState(asset.State.Apply(@event)); + + await asset.WriteStateAsync(version); + } + else if (@event.Payload is ContentEvent contentEvent) + { + var content = await stateFactory.CreateAsync(contentEvent.ContentId); + + content.UpdateState(content.State.Apply(@event)); + + await content.WriteStateAsync(version); + } + else if (@event.Payload is SchemaEvent schemaEvent) + { + var schema = await stateFactory.GetSingleAsync(schemaEvent.SchemaId.Id); + + schema.UpdateState(schema.State.Apply(@event, fieldRegistry)); + + await schema.WriteStateAsync(version); + } + else if (@event.Payload is AppEvent appEvent) + { + var app = await stateFactory.GetSingleAsync(appEvent.AppId.Id); + + app.UpdateState(app.State.Apply(@event)); + + await app.WriteStateAsync(version); + } + } + + timer.Change(5000, 0); + } + catch (Exception ex) + { + subscriptionTcs.SetException(ex); + } + } + + public Task OnErrorAsync(IEventSubscription subscription, Exception exception) + { + subscriptionTcs.TrySetException(exception); + + return TaskHelper.Done; + } + + private Envelope ParseKnownEvent(StoredEvent storedEvent) + { + try + { + return eventDataFormatter.Parse(storedEvent.Data); + } + catch (TypeNameNotFoundException) + { + return null; + } + } + } +} diff --git a/src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs b/tools/Migrate_01/OldEvents/AppClientChanged.cs similarity index 87% rename from src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs rename to tools/Migrate_01/OldEvents/AppClientChanged.cs index 7e459b244..63df3e55a 100644 --- a/src/Squidex.Domain.Apps.Events/Apps/Old/AppClientChanged.cs +++ b/tools/Migrate_01/OldEvents/AppClientChanged.cs @@ -6,13 +6,17 @@ // All rights reserved. // ========================================================================== +using System; using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Events; +using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Events.Apps.Old +namespace Migrate_01.OldEvents { [EventType(nameof(AppClientChanged))] + [Obsolete] public sealed class AppClientChanged : AppEvent, IMigratedEvent { public string Id { get; set; } diff --git a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs b/tools/Migrate_01/OldEvents/ContentArchived.cs similarity index 91% rename from src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs rename to tools/Migrate_01/OldEvents/ContentArchived.cs index 7402dd378..797342e21 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentArchived.cs +++ b/tools/Migrate_01/OldEvents/ContentArchived.cs @@ -8,10 +8,11 @@ using System; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Events.Contents.Old +namespace Migrate_01.OldEvents { [EventType(nameof(ContentArchived))] [Obsolete] diff --git a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs b/tools/Migrate_01/OldEvents/ContentPublished.cs similarity index 91% rename from src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs rename to tools/Migrate_01/OldEvents/ContentPublished.cs index 578eac373..4dd3f9c26 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentPublished.cs +++ b/tools/Migrate_01/OldEvents/ContentPublished.cs @@ -8,10 +8,11 @@ using System; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Events.Contents.Old +namespace Migrate_01.OldEvents { [EventType(nameof(ContentPublished))] [Obsolete] diff --git a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs b/tools/Migrate_01/OldEvents/ContentRestored.cs similarity index 91% rename from src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs rename to tools/Migrate_01/OldEvents/ContentRestored.cs index 5e1526eec..e67d9288e 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentRestored.cs +++ b/tools/Migrate_01/OldEvents/ContentRestored.cs @@ -8,10 +8,11 @@ using System; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Events.Contents.Old +namespace Migrate_01.OldEvents { [EventType(nameof(ContentRestored))] [Obsolete] diff --git a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs b/tools/Migrate_01/OldEvents/ContentUnpublished.cs similarity index 91% rename from src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs rename to tools/Migrate_01/OldEvents/ContentUnpublished.cs index 2908f8ee2..3d22e3dd9 100644 --- a/src/Squidex.Domain.Apps.Events/Contents/Old/ContentUnpublished.cs +++ b/tools/Migrate_01/OldEvents/ContentUnpublished.cs @@ -8,10 +8,11 @@ using System; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; -namespace Squidex.Domain.Apps.Events.Contents.Old +namespace Migrate_01.OldEvents { [EventType(nameof(ContentUnpublished))] [Obsolete] diff --git a/src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookAdded.cs b/tools/Migrate_01/OldEvents/WebhookAdded.cs similarity index 90% rename from src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookAdded.cs rename to tools/Migrate_01/OldEvents/WebhookAdded.cs index f39b91618..8735e9ab4 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookAdded.cs +++ b/tools/Migrate_01/OldEvents/WebhookAdded.cs @@ -7,9 +7,10 @@ // ========================================================================== using System; +using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Events.Schemas.Old +namespace Migrate_01.OldEvents { [EventType(nameof(WebhookAdded))] [Obsolete] diff --git a/src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookDeleted.cs b/tools/Migrate_01/OldEvents/WebhookDeleted.cs similarity index 89% rename from src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookDeleted.cs rename to tools/Migrate_01/OldEvents/WebhookDeleted.cs index c579d224a..3615e36ce 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/Old/WebhookDeleted.cs +++ b/tools/Migrate_01/OldEvents/WebhookDeleted.cs @@ -7,9 +7,10 @@ // ========================================================================== using System; +using Squidex.Domain.Apps.Events; using Squidex.Infrastructure.EventSourcing; -namespace Squidex.Domain.Apps.Events.Schemas.Old +namespace Migrate_01.OldEvents { [EventType(nameof(WebhookDeleted))] [Obsolete]