Browse Source

Merge pull request #206 from Squidex/refactoring-no-cqrs

Refactoring no cqrs
pull/208/head
Sebastian Stehle 8 years ago
committed by GitHub
parent
commit
75be984bc6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      Dockerfile
  2. 3
      Dockerfile.build
  3. 131
      Squidex.sln
  4. 10
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  5. 7
      src/Squidex.Domain.Apps.Core.Model/Contents/IdContentData.cs
  6. 7
      src/Squidex.Domain.Apps.Core.Model/Contents/NamedContentData.cs
  7. 2
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  8. 41
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppEntity.cs
  9. 104
      src/Squidex.Domain.Apps.Entities.MongoDb/Apps/MongoAppRepository.cs
  10. 31
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetEntity.cs
  11. 80
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  12. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs
  13. 22
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs
  14. 8
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs
  15. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  16. 35
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  17. 248
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  18. 58
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  19. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs
  20. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterBuilder.cs
  21. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs
  22. 3
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  23. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/PropertyVisitor.cs
  24. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs
  25. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs
  26. 3
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs
  27. 26
      src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs
  28. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs
  29. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs
  30. 41
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEntity.cs
  31. 6
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs
  32. 22
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  33. 95
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleRepository.cs
  34. 41
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaEntity.cs
  35. 104
      src/Squidex.Domain.Apps.Entities.MongoDb/Schemas/MongoSchemaRepository.cs
  36. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  37. 2
      src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs
  38. 2
      src/Squidex.Domain.Apps.Entities/AppCommand.cs
  39. 150
      src/Squidex.Domain.Apps.Entities/AppProvider.cs
  40. 55
      src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs
  41. 142
      src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs
  42. 2
      src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs
  43. 22
      src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs
  44. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs
  45. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs
  46. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs
  47. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs
  48. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs
  49. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs
  50. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs
  51. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs
  52. 4
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs
  53. 2
      src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs
  54. 7
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs
  55. 4
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs
  56. 8
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs
  57. 6
      src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs
  58. 8
      src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs
  59. 21
      src/Squidex.Domain.Apps.Entities/Apps/Repositories/IAppRepository.cs
  60. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs
  61. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs
  62. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlansProvider.cs
  63. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/IChangePlanResult.cs
  64. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs
  65. 8
      src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppPlansProvider.cs
  66. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs
  67. 3
      src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs
  68. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangedResult.cs
  69. 2
      src/Squidex.Domain.Apps.Entities/Apps/Services/RedirectToCheckoutResult.cs
  70. 107
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  71. 24
      src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
  72. 73
      src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs
  73. 2
      src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs
  74. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs
  75. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs
  76. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs
  77. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs
  78. 2
      src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs
  79. 4
      src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs
  80. 2
      src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs
  81. 2
      src/Squidex.Domain.Apps.Entities/Assets/IAssetStatsEntity.cs
  82. 2
      src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs
  83. 2
      src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetStatsRepository.cs
  84. 91
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  85. 2
      src/Squidex.Domain.Apps.Entities/CachingProviderBase.cs
  86. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs
  87. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs
  88. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs
  89. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs
  90. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/DeleteContent.cs
  91. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs
  92. 2
      src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs
  93. 17
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  94. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentDataChangedResult.cs
  95. 70
      src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs
  96. 4
      src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs
  97. 42
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  98. 21
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  99. 6
      src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs
  100. 2
      src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelExtensions.cs

3
Dockerfile

@ -47,8 +47,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \
RUN dotnet restore \ RUN dotnet restore \
&& dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \ && 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.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.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj && dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
# Publish # Publish

3
Dockerfile.build

@ -44,8 +44,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \
RUN dotnet restore \ RUN dotnet restore \
&& dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \ && 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.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.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj && dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj
# Publish # Publish

131
Squidex.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.27004.2009 VisualStudioVersion = 15.0.27130.2003
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex", "src\Squidex\Squidex.csproj", "{61F6BBCE-A080-4400-B194-70E2F5D2096E}"
EndProject EndProject
@ -12,22 +12,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure", "s
EndProject 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}" 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 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Tests", "tests\Squidex.Infrastructure.Tests\Squidex.Infrastructure.Tests.csproj", "{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}"
EndProject 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}" 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj", "{6A811927-3C37-430A-90F4-503E37123956}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.MongoDb", "src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj", "{6A811927-3C37-430A-90F4-503E37123956}"
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Redis", "src\Squidex.Infrastructure.Redis\Squidex.Infrastructure.Redis.csproj", "{D7166C56-178A-4457-B56A-C615C7450DEE}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.RabbitMq", "src\Squidex.Infrastructure.RabbitMq\Squidex.Infrastructure.RabbitMq.csproj", "{C1E5BBB6-6B6A-4DE5-B19D-0538304DE343}" 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 EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", "{94207AA6-4923-4183-A558-E0F8196B8CA3}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "migrations", "migrations", "{94207AA6-4923-4183-A558-E0F8196B8CA3}"
EndProject 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 EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Shared", "src\Squidex.Shared\Squidex.Shared.csproj", "{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Shared", "src\Squidex.Shared\Squidex.Shared.csproj", "{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F}"
EndProject EndProject
@ -63,7 +53,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Domain.Apps.Core.Mo
EndProject 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}" 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 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|Any CPU.Build.0 = Release|Any CPU
{25F66C64-058A-4D44-BC0C-F12A054F9A91}.Release|x64.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{7FD0A92B-7862-4BB1-932B-B52A9CACB56B}.Debug|Any CPU.Build.0 = 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 {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|Any CPU.Build.0 = Release|Any CPU
{6A811927-3C37-430A-90F4-503E37123956}.Release|x64.ActiveCfg = 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 {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.ActiveCfg = Debug|Any CPU
{D7166C56-178A-4457-B56A-C615C7450DEE}.Debug|Any CPU.Build.0 = 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 {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|x64.Build.0 = Release|Any CPU
{6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x86.ActiveCfg = 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 {6B3F75B6-5888-468E-BA4F-4FC725DAEF31}.Release|x86.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.ActiveCfg = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.Build.0 = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x64.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.ActiveCfg = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.Build.0 = Debug|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Debug|x86.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.Build.0 = Release|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.ActiveCfg = Release|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x64.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.Build.0 = Release|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x64.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.ActiveCfg = Release|Any CPU {79FEF326-CA5E-4698-B2BA-C16A4580B4D5}.Release|x86.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -322,14 +314,9 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {BD1C30A8-8FFA-4A92-A9BD-B67B1CDDD84C} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{25F66C64-058A-4D44-BC0C-F12A054F9A91} = {C9809D59-6665-471E-AD87-5AC624C65892} {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} {7FD0A92B-7862-4BB1-932B-B52A9CACB56B} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {C9809D59-6665-471E-AD87-5AC624C65892} {FD0AFD44-7A93-4F9E-B5ED-72582392E435} = {C9809D59-6665-471E-AD87-5AC624C65892}
{6A811927-3C37-430A-90F4-503E37123956} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF} {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} {D7166C56-178A-4457-B56A-C615C7450DEE} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{C1E5BBB6-6B6A-4DE5-B19D-0538304DE343} = {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} {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} {7931187E-A1E6-4F89-8BC8-20A1E445579F} = {8CF53B92-5EB1-461D-98F8-70DA9B603FBF}
{F0A83301-50A5-40EA-A1A2-07C7858F5A3F} = {C9809D59-6665-471E-AD87-5AC624C65892} {F0A83301-50A5-40EA-A1A2-07C7858F5A3F} = {C9809D59-6665-471E-AD87-5AC624C65892}
{6B3F75B6-5888-468E-BA4F-4FC725DAEF31} = {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 EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08} SolutionGuid = {02F2E872-3141-44F5-BD6A-33CD84E9FE08}

10
src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -31,15 +31,15 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
} }
protected static TResult Merge<TResult>(TResult target, TResult source1, TResult source2) where TResult : ContentData<T> protected static TResult MergeTo<TResult>(TResult target, params TResult[] sources) where TResult : ContentData<T>
{ {
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 source in sources)
{ {
foreach (var otherValue in source) foreach (var otherValue in source)

7
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) public IdContentData MergeInto(IdContentData target)
{ {
return Merge(new IdContentData(), this, target); return Merge(target, this);
} }
public IdContentData ToCleaned() public IdContentData ToCleaned()

7
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) public NamedContentData MergeInto(NamedContentData target)
{ {
return Merge(new NamedContentData(), this, target); return Merge(target, this);
} }
public NamedContentData ToCleaned() public NamedContentData ToCleaned()

2
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -16,7 +16,7 @@
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" /> <PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NJsonSchema" Version="9.10.10" /> <PackageReference Include="NJsonSchema" Version="9.10.13" />
<PackageReference Include="NodaTime" Version="2.2.3" /> <PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="RefactoringEssentials" Version="5.4.0" /> <PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />

41
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; }
}
}

104
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<MongoAppEntity>, IAppRepository, ISnapshotStore<AppState, Guid>
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Apps";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.UserIds));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
}
public async Task<Guid> 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<IReadOnlyList<Guid>> 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<MongoAppEntity>(Projection.Exclude(x => x.Id)).FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion.Version, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

31
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; }
}
}

80
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -12,13 +12,16 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; 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<MongoAssetEntity>, IAssetRepository, IAssetEventConsumer public sealed class MongoAssetRepository : MongoRepositoryBase<MongoAssetEntity>, IAssetRepository, ISnapshotStore<AssetState, Guid>
{ {
public MongoAssetRepository(IMongoDatabase database) public MongoAssetRepository(IMongoDatabase database)
: base(database) : base(database)
@ -27,16 +30,31 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
protected override string CollectionName() protected override string CollectionName()
{ {
return "Projections_Assets"; return "States_Assets";
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetEntity> collection)
{ {
return collection.Indexes.CreateOneAsync( return collection.Indexes.CreateOneAsync(
Index.Ascending(x => x.AppId) Index
.Ascending(x => x.FileName) .Ascending(x => x.State.AppId)
.Ascending(x => x.MimeType) .Ascending(x => x.State.FileName)
.Descending(x => x.LastModified)); .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<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0) public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> 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 filter = CreateFilter(appId, mimeTypes, ids, query);
var assetEntities = 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(); .ToListAsync();
return assetEntities.OfType<IAssetEntity>().ToList(); return assetEntities.OfType<IAssetEntity>().ToList();
@ -63,18 +81,16 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
public async Task<IAssetEntity> FindAssetAsync(Guid id) public async Task<IAssetEntity> FindAssetAsync(Guid id)
{ {
var assetEntity = var (state, etag) = await ReadAsync(id);
await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return assetEntity; return state;
} }
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query) private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query)
{ {
var filters = new List<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {
Filter.Eq(x => x.AppId, appId) Filter.Eq(x => x.State.AppId, appId)
}; };
if (ids != null && ids.Count > 0) if (ids != null && ids.Count > 0)
@ -84,17 +100,47 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
if (mimeTypes != null && mimeTypes.Count > 0) 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)) 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); var filter = Filter.And(filters);
return filter; 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;
}
}
}
} }
} }

6
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsEntity.cs

@ -9,9 +9,9 @@
using System; using System;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes; 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 public sealed class MongoAssetStatsEntity : IAssetStatsEntity
{ {
@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Guid AppId { get; set; } public Guid AssetId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]

22
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetStatsRepository.cs

@ -11,14 +11,15 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public partial class MongoAssetStatsRepository : MongoRepositoryBase<MongoAssetStatsEntity>, IAssetStatsRepository, IAssetEventConsumer public partial class MongoAssetStatsRepository : MongoRepositoryBase<MongoAssetStatsEntity>, IAssetStatsRepository, IEventConsumer
{ {
public MongoAssetStatsRepository(IMongoDatabase database) public MongoAssetStatsRepository(IMongoDatabase database)
: base(database) : base(database)
@ -30,17 +31,16 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
return "Projections_AssetStats"; return "Projections_AssetStats";
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection) protected override async Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
{ {
return Task.WhenAll( await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Ascending(x => x.Date));
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.Date)), await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Date));
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Date)));
} }
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate) public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate)
{ {
var originalSizesEntities = 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(); .ToListAsync();
var enrichedSizes = new List<MongoAssetStatsEntity>(); var enrichedSizes = new List<MongoAssetStatsEntity>();
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
if (previousSize < 0) if (previousSize < 0)
{ {
var firstBeforeRangeEntity = 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(); .FirstOrDefaultAsync();
previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L; previousSize = firstBeforeRangeEntity?.TotalSize ?? 0L;
@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
public async Task<long> GetTotalSizeAsync(Guid appId) public async Task<long> GetTotalSizeAsync(Guid appId)
{ {
var totalSizeEntity = 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(); .FirstOrDefaultAsync();
return totalSizeEntity?.TotalSize ?? 0; return totalSizeEntity?.TotalSize ?? 0;

8
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs → 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.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{ {
public partial class MongoAssetStatsRepository public partial class MongoAssetStatsRepository
{ {
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
public string Name public string Name
{ {
get { return GetType().Name; } get { return GetType().Name; }
@ -60,14 +58,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Assets
if (assetStatsEntity == null) if (assetStatsEntity == null)
{ {
var lastEntity = 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(); .FirstOrDefaultAsync();
assetStatsEntity = new MongoAssetStatsEntity assetStatsEntity = new MongoAssetStatsEntity
{ {
Id = id, Id = id,
Date = date, Date = date,
AppId = appId, AssetId = appId,
TotalSize = lastEntity?.TotalSize ?? 0, TotalSize = lastEntity?.TotalSize ?? 0,
TotalCount = lastEntity?.TotalCount ?? 0 TotalCount = lastEntity?.TotalCount ?? 0
}; };

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Extensions.cs → 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.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public static class Extensions public static class Extensions
{ {

35
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -13,30 +13,23 @@ using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
public sealed class MongoContentEntity : public sealed class MongoContentEntity : IContentEntity
IContentEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy,
IUpdateableEntityWithAppRef
{ {
private NamedContentData data; private NamedContentData data;
[BsonId] [BsonId]
[BsonElement] [BsonElement]
[BsonRepresentation(BsonType.String)] public string DocumentId { get; set; }
public Guid Id { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("st")] [BsonElement]
[BsonRepresentation(BsonType.String)] public Guid Id { get; set; }
public Status Status { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ct")] [BsonElement("ct")]
@ -78,19 +71,29 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
[BsonElement("rd")] [BsonElement("rd")]
public List<Guid> ReferencedIdsDeleted { get; set; } = new List<Guid>(); public List<Guid> ReferencedIdsDeleted { get; set; } = new List<Guid>();
[BsonRequired]
[BsonElement("lt")]
public bool IsLatest { get; set; }
[BsonRequired]
[BsonElement("st")]
[BsonRepresentation(BsonType.String)]
public Status Status { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("do")] [BsonElement("do")]
[BsonJson] [BsonJson]
public IdContentData IdData { get; set; } public IdContentData DataByIds { get; set; }
NamedContentData IContentEntity.Data [BsonIgnore]
public NamedContentData Data
{ {
get { return data; } get { return data; }
} }
public void ParseData(Schema schema) public void ParseData(Schema schema)
{ {
data = IdData.ToData(schema, ReferencedIdsDeleted); data = DataByIds.ToData(schema, ReferencedIdsDeleted);
} }
} }
} }

248
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<MongoContentEntity>,
IEventConsumer,
IContentRepository,
ISnapshotStore<ContentState, Guid>
{
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<MongoContentEntity> 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<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
{
IFindFluent<MongoContentEntity, MongoContentEntity> 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<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
{
IFindFluent<MongoContentEntity, MongoContentEntity> 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<long> CountAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids)
{
var contentsCount =
await Collection.Find(x => ids.Contains(x.Id) && x.IsLatest)
.CountAsync();
return contentsCount;
}
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> 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<IContentEntity>().ToList();
}
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> 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<IContentEntity> 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<IContentEntity> 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;
}
}
}

58
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<IEvent> @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));
}
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/ConstantVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/ConstantVisitor.cs

@ -12,7 +12,7 @@ using Microsoft.OData.UriParser;
using NodaTime; using NodaTime;
using NodaTime.Text; using NodaTime.Text;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{ {
public sealed class ConstantVisitor : QueryNodeVisitor<object> public sealed class ConstantVisitor : QueryNodeVisitor<object>
{ {

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterBuilder.cs → 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.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{ {
public static class FilterBuilder public static class FilterBuilder
{ {

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FilterVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterVisitor.cs

@ -13,7 +13,7 @@ using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas; 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<FilterDefinition<MongoContentEntity>> public class FilterVisitor : QueryNodeVisitor<FilterDefinition<MongoContentEntity>>
{ {

3
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/FindExtensions.cs → 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.Contents;
using Squidex.Domain.Apps.Core.Schemas; 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 public static class FindExtensions
{ {
@ -69,6 +69,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.SchemaId, schemaId), Filter.Eq(x => x.SchemaId, schemaId),
Filter.Eq(x => x.IsLatest, true),
Filter.In(x => x.Status, status) Filter.In(x => x.Status, status)
}; };

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/PropertyVisitor.cs → 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.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas; 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<ImmutableList<string>> public sealed class PropertyVisitor : QueryNodeVisitor<ImmutableList<string>>
{ {

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SearchTermVisitor.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SearchTermVisitor.cs

@ -9,7 +9,7 @@
using System; using System;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{ {
public class SearchTermVisitor : QueryNodeVisitor<string> public class SearchTermVisitor : QueryNodeVisitor<string>
{ {

2
src/Squidex.Domain.Apps.Read.MongoDb/Contents/Visitors/SortBuilder.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/SortBuilder.cs

@ -11,7 +11,7 @@ using Microsoft.OData.UriParser;
using MongoDB.Driver; using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Schemas; 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 public static class SortBuilder
{ {

3
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventEntity.cs

@ -12,11 +12,12 @@ using MongoDB.Bson.Serialization.Attributes;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.History namespace Squidex.Domain.Apps.Entities.MongoDb.History
{ {
public sealed class MongoHistoryEventEntity : MongoEntity, public sealed class MongoHistoryEventEntity : MongoEntity,
IEntity, IEntity,
IEntityWithAppRef, IEntityWithAppRef,
IUpdateableEntity,
IUpdateableEntityWithVersion, IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy, IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithAppRef IUpdateableEntityWithAppRef

26
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/MongoHistoryEventRepository.cs

@ -11,13 +11,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; 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.Events;
using Squidex.Domain.Apps.Read.History;
using Squidex.Domain.Apps.Read.History.Repositories;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.History namespace Squidex.Domain.Apps.Entities.MongoDb.History
{ {
public class MongoHistoryEventRepository : MongoRepositoryBase<MongoHistoryEventEntity>, IHistoryEventRepository, IEventConsumer public class MongoHistoryEventRepository : MongoRepositoryBase<MongoHistoryEventEntity>, IHistoryEventRepository, IEventConsumer
{ {
@ -53,23 +53,22 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
return "Projections_History"; return "Projections_History";
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection) protected override async Task SetupCollectionAsync(IMongoCollection<MongoHistoryEventEntity> collection)
{ {
return Task.WhenAll( await collection.Indexes.CreateOneAsync(
collection.Indexes.CreateOneAsync(
Index Index
.Ascending(x => x.AppId) .Ascending(x => x.AppId)
.Ascending(x => x.Channel) .Ascending(x => x.Channel)
.Descending(x => x.Created) .Descending(x => x.Created)
.Descending(x => x.Version)), .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.Created), new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(365) });
} }
public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count) public async Task<IReadOnlyList<IHistoryEventEntity>> QueryByChannelAsync(Guid appId, string channelPrefix, int count)
{ {
var historyEventEntities = var historyEventEntities =
await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix) await Collection.Find(x => x.AppId == appId && x.Channel == channelPrefix).SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
.SortByDescending(x => x.Created).ThenByDescending(x => x.Version).Limit(count)
.ToListAsync(); .ToListAsync();
return historyEventEntities.Select(x => (IHistoryEventEntity)new ParsedHistoryEvent(x, texts)).ToList(); 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.AppId = appEvent.AppId.Id;
if (@event.Headers.Contains(CommonHeaders.SnapshotVersion))
{
entity.Version = @event.Headers.SnapshotVersion();
}
else
{
entity.Version = @event.Headers.EventStreamNumber(); entity.Version = @event.Headers.EventStreamNumber();
}
entity.Channel = message.Channel; entity.Channel = message.Channel;
entity.Message = message.Message; entity.Message = message.Message;

4
src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs → src/Squidex.Domain.Apps.Entities.MongoDb/History/ParsedHistoryEvent.cs

@ -9,12 +9,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Read.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure; 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 #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 internal sealed class ParsedHistoryEvent : IHistoryEventEntity
{ {

4
src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs → src/Squidex.Domain.Apps.Entities.MongoDb/MongoCollectionExtensions.cs

@ -14,13 +14,13 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb namespace Squidex.Domain.Apps.Entities.MongoDb
{ {
public static class MongoCollectionExtensions public static class MongoCollectionExtensions
{ {
public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new() public static Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> 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); return collection.InsertOneIfNotExistsAsync(entity);
} }

41
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; }
}
}

6
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventEntity.cs

@ -11,16 +11,16 @@ using MongoDB.Bson.Serialization.Attributes;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
public sealed class MongoRuleEventEntity : MongoEntity, IRuleEventEntity public sealed class MongoRuleEventEntity : MongoEntity, IRuleEventEntity
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public Guid AppId { get; set; } public Guid AssetId { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]

22
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -14,12 +14,12 @@ using MongoDB.Driver;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Read.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Read.Rules.Repositories; using Squidex.Domain.Apps.Entities.Rules.Repositories;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{ {
public sealed class MongoRuleEventRepository : MongoRepositoryBase<MongoRuleEventEntity>, IRuleEventRepository public sealed class MongoRuleEventRepository : MongoRepositoryBase<MongoRuleEventEntity>, IRuleEventRepository
{ {
@ -33,12 +33,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
return "RuleEvents"; return "RuleEvents";
} }
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection) protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection)
{ {
return Task.WhenAll( await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt));
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt)), await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AssetId).Descending(x => x.Created));
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Created)), await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero });
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Expires), new CreateIndexOptions { ExpireAfter = TimeSpan.Zero }));
} }
public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)) public Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken))
@ -49,7 +48,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public async Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20) public async Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20)
{ {
var ruleEventEntities = 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(); .ToListAsync();
return ruleEventEntities; return ruleEventEntities;
@ -66,7 +65,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public async Task<int> CountByAppAsync(Guid appId) public async Task<int> 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) public Task EnqueueAsync(Guid id, Instant nextAttempt)
@ -84,7 +83,8 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextAttempt) public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextAttempt)
{ {
return Collection.UpdateOneAsync(x => x.Id == jobId, return Collection.UpdateOneAsync(x => x.Id == jobId,
Update.Set(x => x.Result, result) Update
.Set(x => x.Result, result)
.Set(x => x.LastDump, dump) .Set(x => x.LastDump, dump)
.Set(x => x.JobResult, jobResult) .Set(x => x.JobResult, jobResult)
.Set(x => x.NextAttempt, nextAttempt) .Set(x => x.NextAttempt, nextAttempt)

95
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<MongoRuleEntity>, IRuleRepository, ISnapshotStore<RuleState, Guid>
{
public MongoRuleRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Rules";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> 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<IReadOnlyList<Guid>> 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;
}
}
}
}
}

41
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; }
}
}

104
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<MongoSchemaEntity>, ISchemaRepository, ISnapshotStore<SchemaState, Guid>
{
public MongoSchemaRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "States_Schemas";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> 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<Guid> 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<IReadOnlyList<Guid>> 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;
}
}
}
}
}

2
src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj → src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -12,7 +12,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" /> <ProjectReference Include="..\Squidex.Domain.Apps.Events\Squidex.Domain.Apps.Events.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
<ProjectReference Include="..\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" />
<ProjectReference Include="..\Squidex.Domain.Apps.Read\Squidex.Domain.Apps.Read.csproj" /> <ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" /> <PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />

2
src/Squidex.Domain.Apps.Write/AppAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/AppAggregateCommand.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write namespace Squidex.Domain.Apps.Entities
{ {
public class AppAggregateCommand : AppCommand, IAggregateCommand public class AppAggregateCommand : AppCommand, IAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/AppCommand.cs → src/Squidex.Domain.Apps.Entities/AppCommand.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write namespace Squidex.Domain.Apps.Entities
{ {
public abstract class AppCommand : SquidexCommand public abstract class AppCommand : SquidexCommand
{ {

150
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<AppDomainObject>(appId);
if (IsNotFound(app))
{
return (null, null);
}
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id);
return IsNotFound(false, schema) ? (null, null) : (app.State, schema.State);
}
public async Task<IAppEntity> GetAppAsync(string appName)
{
var appId = await GetAppIdAsync(appName);
if (appId == Guid.Empty)
{
return null;
}
var app = await stateFactory.GetSingleAsync<AppDomainObject>(appId);
return IsNotFound(app) ? null : app.State;
}
public async Task<ISchemaEntity> 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<SchemaDomainObject>(schemaId);
return IsNotFound(provideDeleted, schema) ? null : schema.State;
}
public async Task<ISchemaEntity> GetSchemaAsync(Guid appId, Guid id, bool provideDeleted = false)
{
var schema = await stateFactory.GetSingleAsync<SchemaDomainObject>(id);
return IsNotFound(provideDeleted, schema) ? null : schema.State;
}
public async Task<List<ISchemaEntity>> GetSchemasAsync(Guid appId)
{
var ids = await schemaRepository.QuerySchemaIdsAsync(appId);
var schemas =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<SchemaDomainObject>(id)));
return schemas.Select(a => (ISchemaEntity)a.State).ToList();
}
public async Task<List<IRuleEntity>> GetRulesAsync(Guid appId)
{
var ids = await ruleRepository.QueryRuleIdsAsync(appId);
var rules =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<RuleDomainObject>(id)));
return rules.Select(a => (IRuleEntity)a.State).ToList();
}
public async Task<List<IAppEntity>> GetUserApps(string userId)
{
var ids = await appRepository.QueryUserAppIdsAsync(userId);
var apps =
await Task.WhenAll(
ids.Select(id => stateFactory.GetSingleAsync<AppDomainObject>(id)));
return apps.Select(a => (IAppEntity)a.State).ToList();
}
private Task<Guid> GetAppIdAsync(string name)
{
return appRepository.FindAppIdByNameAsync(name);
}
private Task<Guid> 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);
}
}
}

55
src/Squidex.Domain.Apps.Write/Apps/AppCommandMiddleware.cs → src/Squidex.Domain.Apps.Entities/Apps/AppCommandMiddleware.cs

@ -8,16 +8,15 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Guards;
using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Guards;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Write.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public class AppCommandMiddleware : ICommandMiddleware public class AppCommandMiddleware : ICommandMiddleware
{ {
@ -47,23 +46,23 @@ namespace Squidex.Domain.Apps.Write.Apps
this.appPlansBillingManager = appPlansBillingManager; this.appPlansBillingManager = appPlansBillingManager;
} }
protected async Task On(CreateApp command, CommandContext context) protected Task On(CreateApp command, CommandContext context)
{ {
await handler.CreateAsync<AppDomainObject>(context, async a => return handler.CreateSyncedAsync<AppDomainObject>(context, async a =>
{ {
await GuardApp.CanCreate(command, appProvider); await GuardApp.CanCreate(command, appProvider);
a.Create(command); 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<AppDomainObject>(context, async a => return handler.UpdateSyncedAsync<AppDomainObject>(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); a.AssignContributor(command);
}); });
@ -71,9 +70,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(RemoveContributor command, CommandContext context) protected Task On(RemoveContributor command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppContributors.CanRemove(a.Contributors, command); GuardAppContributors.CanRemove(a.State.Contributors, command);
a.RemoveContributor(command); a.RemoveContributor(command);
}); });
@ -81,9 +80,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(AttachClient command, CommandContext context) protected Task On(AttachClient command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanAttach(a.Clients, command); GuardAppClients.CanAttach(a.State.Clients, command);
a.AttachClient(command); a.AttachClient(command);
}); });
@ -91,9 +90,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(UpdateClient command, CommandContext context) protected Task On(UpdateClient command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanUpdate(a.Clients, command); GuardAppClients.CanUpdate(a.State.Clients, command);
a.UpdateClient(command); a.UpdateClient(command);
}); });
@ -101,9 +100,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(RevokeClient command, CommandContext context) protected Task On(RevokeClient command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppClients.CanRevoke(a.Clients, command); GuardAppClients.CanRevoke(a.State.Clients, command);
a.RevokeClient(command); a.RevokeClient(command);
}); });
@ -111,9 +110,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(AddLanguage command, CommandContext context) protected Task On(AddLanguage command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanAdd(a.LanguagesConfig, command); GuardAppLanguages.CanAdd(a.State.LanguagesConfig, command);
a.AddLanguage(command); a.AddLanguage(command);
}); });
@ -121,9 +120,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(RemoveLanguage command, CommandContext context) protected Task On(RemoveLanguage command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanRemove(a.LanguagesConfig, command); GuardAppLanguages.CanRemove(a.State.LanguagesConfig, command);
a.RemoveLanguage(command); a.RemoveLanguage(command);
}); });
@ -131,9 +130,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(UpdateLanguage command, CommandContext context) protected Task On(UpdateLanguage command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, a => return handler.UpdateSyncedAsync<AppDomainObject>(context, a =>
{ {
GuardAppLanguages.CanUpdate(a.LanguagesConfig, command); GuardAppLanguages.CanUpdate(a.State.LanguagesConfig, command);
a.UpdateLanguage(command); a.UpdateLanguage(command);
}); });
@ -141,9 +140,9 @@ namespace Squidex.Domain.Apps.Write.Apps
protected Task On(ChangePlan command, CommandContext context) protected Task On(ChangePlan command, CommandContext context)
{ {
return handler.UpdateAsync<AppDomainObject>(context, async a => return handler.UpdateSyncedAsync<AppDomainObject>(context, async a =>
{ {
GuardApp.CanChangePlan(command, a.Plan, appPlansProvider); GuardApp.CanChangePlan(command, a.State.Plan, appPlansProvider);
if (command.FromCallback) if (command.FromCallback)
{ {
@ -151,7 +150,7 @@ namespace Squidex.Domain.Apps.Write.Apps
} }
else 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) if (result is PlanChangedResult)
{ {

142
src/Squidex.Domain.Apps.Write/Apps/AppDomainObject.cs → src/Squidex.Domain.Apps.Entities/Apps/AppDomainObject.cs

@ -8,130 +8,41 @@
using System; using System;
using Squidex.Domain.Apps.Core.Apps; 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;
using Squidex.Domain.Apps.Events.Apps; 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;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; 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<AppState>
{ {
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<IEvent> @event)
{
this.DispatchAction(@event.Payload);
}
public AppDomainObject Create(CreateApp command) public AppDomainObject Create(CreateApp command)
{ {
ThrowIfCreated(); ThrowIfCreated();
var appId = new NamedId<Guid>(command.AppId, command.Name); var appId = new NamedId<Guid>(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, CreateInitialOwner(appId, command)));
RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId))); RaiseEvent(SimpleMapper.Map(command, CreateInitialLanguage(appId)));
return this; return this;
} }
public AppDomainObject UpdateLanguage(UpdateLanguage command)
{
ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated()));
return this;
}
public AppDomainObject UpdateClient(UpdateClient command) public AppDomainObject UpdateClient(UpdateClient command)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
@ -203,15 +114,6 @@ namespace Squidex.Domain.Apps.Write.Apps
return this; return this;
} }
public AppDomainObject UpdateLanguage(UpdateLanguage command)
{
ThrowIfNotCreated();
RaiseEvent(SimpleMapper.Map(command, new AppLanguageUpdated()));
return this;
}
public AppDomainObject ChangePlan(ChangePlan command) public AppDomainObject ChangePlan(ChangePlan command)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
@ -225,12 +127,17 @@ namespace Squidex.Domain.Apps.Write.Apps
{ {
if (@event.AppId == null) if (@event.AppId == null)
{ {
@event.AppId = new NamedId<Guid>(Id, name); @event.AppId = new NamedId<Guid>(State.Id, State.Name);
} }
RaiseEvent(Envelope.Create(@event)); RaiseEvent(Envelope.Create(@event));
} }
private static AppCreated CreateInitalEvent(NamedId<Guid> appId)
{
return new AppCreated { AppId = appId };
}
private static AppLanguageAdded CreateInitialLanguage(NamedId<Guid> id) private static AppLanguageAdded CreateInitialLanguage(NamedId<Guid> id)
{ {
return new AppLanguageAdded { AppId = id, Language = Language.EN }; return new AppLanguageAdded { AppId = id, Language = Language.EN };
@ -243,7 +150,7 @@ namespace Squidex.Domain.Apps.Write.Apps
private void ThrowIfNotCreated() private void ThrowIfNotCreated()
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(State.Name))
{ {
throw new DomainException("App has not been created."); throw new DomainException("App has not been created.");
} }
@ -251,10 +158,15 @@ namespace Squidex.Domain.Apps.Write.Apps
private void ThrowIfCreated() private void ThrowIfCreated()
{ {
if (!string.IsNullOrWhiteSpace(name)) if (!string.IsNullOrWhiteSpace(State.Name))
{ {
throw new DomainException("App has already been created."); throw new DomainException("App has already been created.");
} }
} }
protected override void OnRaised(Envelope<IEvent> @event)
{
UpdateState(State.Apply(@event));
}
} }
} }

2
src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs → src/Squidex.Domain.Apps.Entities/Apps/AppEntityExtensions.cs

@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
namespace Squidex.Domain.Apps.Read.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public static class AppEntityExtensions public static class AppEntityExtensions
{ {

22
src/Squidex.Domain.Apps.Read/Apps/AppHistoryEventsCreator.cs → src/Squidex.Domain.Apps.Entities/Apps/AppHistoryEventsCreator.cs

@ -7,13 +7,13 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Read.History;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Read.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public class AppHistoryEventsCreator : HistoryEventsCreatorBase public class AppHistoryEventsCreator : HistoryEventsCreatorBase
{ {
@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Read.Apps
"changed master language to {[Language]}"); "changed master language to {[Language]}");
} }
protected Task<HistoryEventToStore> On(AppContributorRemoved @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppContributorRemoved @event)
{ {
const string channel = "settings.contributors"; const string channel = "settings.contributors";
@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Contributor", @event.ContributorId)); .AddParameter("Contributor", @event.ContributorId));
} }
protected Task<HistoryEventToStore> On(AppContributorAssigned @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppContributorAssigned @event)
{ {
const string channel = "settings.contributors"; const string channel = "settings.contributors";
@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Contributor", @event.ContributorId).AddParameter("Permission", @event.Permission)); .AddParameter("Contributor", @event.ContributorId).AddParameter("Permission", @event.Permission));
} }
protected Task<HistoryEventToStore> On(AppClientAttached @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppClientAttached @event)
{ {
const string channel = "settings.clients"; const string channel = "settings.clients";
@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Id", @event.Id)); .AddParameter("Id", @event.Id));
} }
protected Task<HistoryEventToStore> On(AppClientRevoked @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppClientRevoked @event)
{ {
const string channel = "settings.clients"; const string channel = "settings.clients";
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Id", @event.Id)); .AddParameter("Id", @event.Id));
} }
protected Task<HistoryEventToStore> On(AppClientRenamed @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppClientRenamed @event)
{ {
const string channel = "settings.clients"; const string channel = "settings.clients";
@ -96,7 +96,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Id", @event.Id).AddParameter("Name", ClientName(@event))); .AddParameter("Id", @event.Id).AddParameter("Name", ClientName(@event)));
} }
protected Task<HistoryEventToStore> On(AppLanguageAdded @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppLanguageAdded @event)
{ {
const string channel = "settings.languages"; const string channel = "settings.languages";
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Language", @event.Language)); .AddParameter("Language", @event.Language));
} }
protected Task<HistoryEventToStore> On(AppLanguageRemoved @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppLanguageRemoved @event)
{ {
const string channel = "settings.languages"; const string channel = "settings.languages";
@ -114,7 +114,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Language", @event.Language)); .AddParameter("Language", @event.Language));
} }
protected Task<HistoryEventToStore> On(AppLanguageUpdated @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppLanguageUpdated @event)
{ {
const string channel = "settings.languages"; const string channel = "settings.languages";
@ -123,7 +123,7 @@ namespace Squidex.Domain.Apps.Read.Apps
.AddParameter("Language", @event.Language)); .AddParameter("Language", @event.Language));
} }
protected Task<HistoryEventToStore> On(AppMasterLanguageSet @event, EnvelopeHeaders headers) protected Task<HistoryEventToStore> On(AppMasterLanguageSet @event)
{ {
const string channel = "settings.languages"; const string channel = "settings.languages";

2
src/Squidex.Domain.Apps.Write/Apps/Commands/AddLanguage.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/AddLanguage.cs

@ -8,7 +8,7 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AddLanguage : AppAggregateCommand public sealed class AddLanguage : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/AssignContributor.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/AssignContributor.cs

@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core.Apps; 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 public sealed class AssignContributor : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/AttachClient.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/AttachClient.cs

@ -8,7 +8,7 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class AttachClient : AppAggregateCommand public sealed class AttachClient : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/ChangePlan.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/ChangePlan.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class ChangePlan : AppAggregateCommand public sealed class ChangePlan : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/CreateApp.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/CreateApp.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class CreateApp : SquidexCommand, IAggregateCommand public sealed class CreateApp : SquidexCommand, IAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveContributor.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveContributor.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RemoveContributor : AppAggregateCommand public sealed class RemoveContributor : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/RemoveLanguage.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/RemoveLanguage.cs

@ -8,7 +8,7 @@
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RemoveLanguage : AppAggregateCommand public sealed class RemoveLanguage : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/RevokeClient.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/RevokeClient.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class RevokeClient : AppAggregateCommand public sealed class RevokeClient : AppAggregateCommand
{ {

4
src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateClient.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateClient.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// RenameClient.cs // UpdateClient.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core.Apps; 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 public sealed class UpdateClient : AppAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Apps/Commands/UpdateLanguage.cs → src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateLanguage.cs

@ -9,7 +9,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Commands namespace Squidex.Domain.Apps.Entities.Apps.Commands
{ {
public sealed class UpdateLanguage : AppAggregateCommand public sealed class UpdateLanguage : AppAggregateCommand
{ {

7
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardApp.cs → src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs

@ -9,12 +9,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Domain.Apps.Write.Apps.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public static class GuardApp public static class GuardApp
{ {

4
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppClients.cs → src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs

@ -7,10 +7,10 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Apps; 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;
namespace Squidex.Domain.Apps.Write.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public static class GuardAppClients public static class GuardAppClients
{ {

8
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppContributors.cs → src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// GuardApp.cs // GuardAppContributors.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -9,12 +9,12 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Write.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Write.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public static class GuardAppContributors public static class GuardAppContributors
{ {

6
src/Squidex.Domain.Apps.Write/Apps/Guards/GuardAppLanguages.cs → src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// GuardApp.cs // GuardAppLanguages.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -7,10 +7,10 @@
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core.Apps; 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;
namespace Squidex.Domain.Apps.Write.Apps.Guards namespace Squidex.Domain.Apps.Entities.Apps.Guards
{ {
public static class GuardAppLanguages public static class GuardAppLanguages
{ {

8
src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs → src/Squidex.Domain.Apps.Entities/Apps/IAppEntity.cs

@ -8,17 +8,13 @@
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Read.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppEntity : IEntity, IEntityWithVersion public interface IAppEntity : IEntity, IEntityWithVersion
{ {
string Etag { get; }
string Name { get; } string Name { get; }
string PlanId { get; } AppPlan Plan { get; }
string PlanOwner { get; }
AppClients Clients { get; } AppClients Clients { get; }

21
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<Guid> FindAppIdByNameAsync(string name);
Task<IReadOnlyList<Guid>> QueryUserAppIdsAsync(string userId);
}
}

2
src/Squidex.Domain.Apps.Read/Apps/Services/IAppLimitsPlan.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/IAppLimitsPlan.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public interface IAppLimitsPlan public interface IAppLimitsPlan
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlanBillingManager.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs

@ -9,7 +9,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public interface IAppPlanBillingManager public interface IAppPlanBillingManager
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/IAppPlansProvider.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlansProvider.cs

@ -8,7 +8,7 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public interface IAppPlansProvider public interface IAppPlansProvider
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/IChangePlanResult.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/IChangePlanResult.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public interface IChangePlanResult public interface IChangePlanResult
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppLimitsPlan.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppLimitsPlan.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
{ {
public sealed class ConfigAppLimitsPlan : IAppLimitsPlan public sealed class ConfigAppLimitsPlan : IAppLimitsPlan
{ {

8
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/ConfigAppPlansProvider.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/ConfigAppPlansProvider.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// ConfigAppLimitsProvider.cs // ConfigAppPlansProvider.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
{ {
public sealed class ConfigAppPlansProvider : IAppPlansProvider public sealed class ConfigAppPlansProvider : IAppPlansProvider
{ {
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
return GetPlan(app.PlanId); return GetPlan(app.Plan?.PlanId);
} }
public IAppLimitsPlan GetPlan(string planId) public IAppLimitsPlan GetPlan(string planId)
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
return GetPlanUpgrade(app.PlanId); return GetPlanUpgrade(app.Plan?.PlanId);
} }
public IAppLimitsPlan GetPlanUpgrade(string planId) public IAppLimitsPlan GetPlanUpgrade(string planId)

2
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/NoopAppPlanBillingManager.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs

@ -9,7 +9,7 @@
using System; using System;
using System.Threading.Tasks; 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 public sealed class NoopAppPlanBillingManager : IAppPlanBillingManager
{ {

3
src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangeAsyncResult.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangeAsyncResult.cs

@ -5,7 +5,8 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Read.Apps.Services
namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public sealed class PlanChangeAsyncResult : IChangePlanResult public sealed class PlanChangeAsyncResult : IChangePlanResult
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/PlanChangedResult.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/PlanChangedResult.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public sealed class PlanChangedResult : IChangePlanResult public sealed class PlanChangedResult : IChangePlanResult
{ {

2
src/Squidex.Domain.Apps.Read/Apps/Services/RedirectToCheckoutResult.cs → src/Squidex.Domain.Apps.Entities/Apps/Services/RedirectToCheckoutResult.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Apps.Services namespace Squidex.Domain.Apps.Entities.Apps.Services
{ {
public sealed class RedirectToCheckoutResult : IChangePlanResult public sealed class RedirectToCheckoutResult : IChangePlanResult
{ {

107
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<AppState>,
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<IEvent> @event)
{
var payload = (SquidexEvent)@event.Payload;
return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload));
}
}
}

24
src/Squidex.Domain.Apps.Write/Assets/AssetCommandMiddleware.cs → src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs

@ -8,14 +8,14 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Write.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Write.Assets.Guards; using Squidex.Domain.Apps.Entities.Assets.Guards;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
namespace Squidex.Domain.Apps.Write.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public class AssetCommandMiddleware : ICommandMiddleware public class AssetCommandMiddleware : ICommandMiddleware
{ {
@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Write.Assets
command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead());
try try
{ {
var asset = await handler.CreateAsync<AssetDomainObject>(context, async a => var asset = await handler.CreateSyncedAsync<AssetDomainObject>(context, async a =>
{ {
GuardAsset.CanCreate(command); GuardAsset.CanCreate(command);
@ -50,10 +50,10 @@ namespace Squidex.Domain.Apps.Write.Assets
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); 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 finally
{ {
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Write.Assets
try try
{ {
var asset = await handler.UpdateAsync<AssetDomainObject>(context, async a => var asset = await handler.UpdateSyncedAsync<AssetDomainObject>(context, async a =>
{ {
GuardAsset.CanUpdate(command); GuardAsset.CanUpdate(command);
@ -75,10 +75,10 @@ namespace Squidex.Domain.Apps.Write.Assets
await assetStore.UploadTemporaryAsync(context.ContextId.ToString(), command.File.OpenRead()); 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 finally
{ {
@ -88,9 +88,9 @@ namespace Squidex.Domain.Apps.Write.Assets
protected Task On(RenameAsset command, CommandContext context) protected Task On(RenameAsset command, CommandContext context)
{ {
return handler.UpdateAsync<AssetDomainObject>(context, a => return handler.UpdateSyncedAsync<AssetDomainObject>(context, a =>
{ {
GuardAsset.CanRename(command, a.FileName); GuardAsset.CanRename(command, a.State.FileName);
a.Rename(command); a.Rename(command);
}); });
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Write.Assets
protected Task On(DeleteAsset command, CommandContext context) protected Task On(DeleteAsset command, CommandContext context)
{ {
return handler.UpdateAsync<AssetDomainObject>(context, a => return handler.UpdateSyncedAsync<AssetDomainObject>(context, a =>
{ {
GuardAsset.CanDelete(command); GuardAsset.CanDelete(command);

73
src/Squidex.Domain.Apps.Write/Assets/AssetDomainObject.cs → src/Squidex.Domain.Apps.Entities/Assets/AssetDomainObject.cs

@ -6,69 +6,18 @@
// All rights reserved. // 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.Events.Assets;
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; 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<AssetState>
{ {
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) public AssetDomainObject Create(CreateAsset command)
{ {
VerifyNotCreated(); VerifyNotCreated();
@ -77,7 +26,7 @@ namespace Squidex.Domain.Apps.Write.Assets
{ {
FileName = command.File.FileName, FileName = command.File.FileName,
FileSize = command.File.FileSize, FileSize = command.File.FileSize,
FileVersion = fileVersion + 1, FileVersion = 0,
MimeType = command.File.MimeType, MimeType = command.File.MimeType,
PixelWidth = command.ImageInfo?.PixelWidth, PixelWidth = command.ImageInfo?.PixelWidth,
PixelHeight = command.ImageInfo?.PixelHeight, PixelHeight = command.ImageInfo?.PixelHeight,
@ -95,7 +44,7 @@ namespace Squidex.Domain.Apps.Write.Assets
var @event = SimpleMapper.Map(command, new AssetUpdated var @event = SimpleMapper.Map(command, new AssetUpdated
{ {
FileVersion = fileVersion + 1, FileVersion = State.FileVersion + 1,
FileSize = command.File.FileSize, FileSize = command.File.FileSize,
MimeType = command.File.MimeType, MimeType = command.File.MimeType,
PixelWidth = command.ImageInfo?.PixelWidth, PixelWidth = command.ImageInfo?.PixelWidth,
@ -112,7 +61,7 @@ namespace Squidex.Domain.Apps.Write.Assets
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = totalSize })); RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = State.TotalSize }));
return this; return this;
} }
@ -128,7 +77,7 @@ namespace Squidex.Domain.Apps.Write.Assets
private void VerifyNotCreated() private void VerifyNotCreated()
{ {
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(State.FileName))
{ {
throw new DomainException("Asset has already been created."); throw new DomainException("Asset has already been created.");
} }
@ -136,15 +85,15 @@ namespace Squidex.Domain.Apps.Write.Assets
private void VerifyCreatedAndNotDeleted() 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."); throw new DomainException("Asset has already been deleted or not created yet.");
} }
} }
protected override void DispatchEvent(Envelope<IEvent> @event) protected override void OnRaised(Envelope<IEvent> @event)
{ {
this.DispatchAction(@event.Payload); UpdateState(State.Apply(@event));
} }
} }
} }

2
src/Squidex.Domain.Apps.Write/Assets/AssetSavedResult.cs → src/Squidex.Domain.Apps.Entities/Assets/AssetSavedResult.cs

@ -8,7 +8,7 @@
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public class AssetSavedResult : EntitySavedResult public class AssetSavedResult : EntitySavedResult
{ {

2
src/Squidex.Domain.Apps.Write/Assets/Commands/AssetAggregateCommand.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/AssetAggregateCommand.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand public abstract class AssetAggregateCommand : AppCommand, IAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Assets/Commands/CreateAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs

@ -9,7 +9,7 @@
using System; using System;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class CreateAsset : AssetAggregateCommand public sealed class CreateAsset : AssetAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Assets/Commands/DeleteAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/DeleteAsset.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class DeleteAsset : AssetAggregateCommand public sealed class DeleteAsset : AssetAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Assets/Commands/RenameAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/RenameAsset.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class RenameAsset : AssetAggregateCommand public sealed class RenameAsset : AssetAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Assets/Commands/UpdateAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs

@ -8,7 +8,7 @@
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
namespace Squidex.Domain.Apps.Write.Assets.Commands namespace Squidex.Domain.Apps.Entities.Assets.Commands
{ {
public sealed class UpdateAsset : AssetAggregateCommand public sealed class UpdateAsset : AssetAggregateCommand
{ {

4
src/Squidex.Domain.Apps.Write/Assets/Guards/GuardAsset.cs → src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs

@ -6,10 +6,10 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Write.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Write.Assets.Guards namespace Squidex.Domain.Apps.Entities.Assets.Guards
{ {
public static class GuardAsset public static class GuardAsset
{ {

2
src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs → src/Squidex.Domain.Apps.Entities/Assets/IAssetEntity.cs

@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
namespace Squidex.Domain.Apps.Read.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetEntity : public interface IAssetEntity :
IEntity, IEntity,

2
src/Squidex.Domain.Apps.Read/Assets/IAssetStatsEntity.cs → src/Squidex.Domain.Apps.Entities/Assets/IAssetStatsEntity.cs

@ -8,7 +8,7 @@
using System; using System;
namespace Squidex.Domain.Apps.Read.Assets namespace Squidex.Domain.Apps.Entities.Assets
{ {
public interface IAssetStatsEntity public interface IAssetStatsEntity
{ {

2
src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetRepository.cs → src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs

@ -10,7 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Assets.Repositories namespace Squidex.Domain.Apps.Entities.Assets.Repositories
{ {
public interface IAssetRepository public interface IAssetRepository
{ {

2
src/Squidex.Domain.Apps.Read/Assets/Repositories/IAssetStatsRepository.cs → src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetStatsRepository.cs

@ -10,7 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Assets.Repositories namespace Squidex.Domain.Apps.Entities.Assets.Repositories
{ {
public interface IAssetStatsRepository public interface IAssetStatsRepository
{ {

91
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<AssetState>,
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<IEvent> @event)
{
var payload = (SquidexEvent)@event.Payload;
return Clone().Update(payload, @event.Headers, r => r.DispatchAction(payload));
}
}
}

2
src/Squidex.Domain.Apps.Read/CachingProviderBase.cs → src/Squidex.Domain.Apps.Entities/CachingProviderBase.cs

@ -9,7 +9,7 @@
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read namespace Squidex.Domain.Apps.Entities
{ {
public abstract class CachingProviderBase public abstract class CachingProviderBase
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/ChangeContentStatus.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/ChangeContentStatus.cs

@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core.Contents; 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 public sealed class ChangeContentStatus : ContentCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/ContentCommand.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentCommand.cs

@ -10,7 +10,7 @@ using System;
using System.Security.Claims; using System.Security.Claims;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public abstract class ContentCommand : SchemaCommand, IAggregateCommand public abstract class ContentCommand : SchemaCommand, IAggregateCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/ContentDataCommand.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/ContentDataCommand.cs

@ -8,7 +8,7 @@
using Squidex.Domain.Apps.Core.Contents; 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 public abstract class ContentDataCommand : ContentCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/CreateContent.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/CreateContent.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class CreateContent : ContentDataCommand public sealed class CreateContent : ContentDataCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/DeleteContent.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/DeleteContent.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class DeleteContent : ContentCommand public sealed class DeleteContent : ContentCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/PatchContent.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/PatchContent.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class PatchContent : ContentDataCommand public sealed class PatchContent : ContentDataCommand
{ {

2
src/Squidex.Domain.Apps.Write/Contents/Commands/UpdateContent.cs → src/Squidex.Domain.Apps.Entities/Contents/Commands/UpdateContent.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Domain.Apps.Write.Contents.Commands namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class UpdateContent : ContentDataCommand public sealed class UpdateContent : ContentDataCommand
{ {

17
src/Squidex.Domain.Apps.Write/Contents/ContentCommandMiddleware.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -9,16 +9,15 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Guards;
using Squidex.Domain.Apps.Write.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Write.Contents.Guards;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching; using Squidex.Infrastructure.Dispatching;
namespace Squidex.Domain.Apps.Write.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public class ContentCommandMiddleware : ICommandMiddleware public class ContentCommandMiddleware : ICommandMiddleware
{ {
@ -84,7 +83,7 @@ namespace Squidex.Domain.Apps.Write.Contents
content.Update(command); 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); 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<ContentDomainObject>(context, async content => return handler.UpdateAsync<ContentDomainObject>(context, async content =>
{ {
GuardContent.CanChangeContentStatus(content.Status, command); GuardContent.CanChangeContentStatus(content.State.Status, command);
var operationContext = await CreateContext(command, content, () => "Failed to patch content."); var operationContext = await CreateContext(command, content, () => "Failed to patch content.");

2
src/Squidex.Domain.Apps.Write/Contents/ContentDataChangedResult.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentDataChangedResult.cs

@ -9,7 +9,7 @@
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
namespace Squidex.Domain.Apps.Write.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentDataChangedResult : EntitySavedResult public sealed class ContentDataChangedResult : EntitySavedResult
{ {

70
src/Squidex.Domain.Apps.Write/Contents/ContentDomainObject.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs

@ -6,67 +6,19 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Core.Contents; 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.Events.Contents;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; 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<ContentState>
{ {
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) public ContentDomainObject Create(CreateContent command)
{ {
VerifyNotCreated(); VerifyNotCreated();
@ -103,7 +55,7 @@ namespace Squidex.Domain.Apps.Write.Contents
{ {
VerifyCreatedAndNotDeleted(); VerifyCreatedAndNotDeleted();
if (!command.Data.Equals(Data)) if (!command.Data.Equals(State.Data))
{ {
RaiseEvent(SimpleMapper.Map(command, new ContentUpdated())); RaiseEvent(SimpleMapper.Map(command, new ContentUpdated()));
} }
@ -115,9 +67,9 @@ namespace Squidex.Domain.Apps.Write.Contents
{ {
VerifyCreatedAndNotDeleted(); 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()); var @event = SimpleMapper.Map(command, new ContentUpdated());
@ -131,7 +83,7 @@ namespace Squidex.Domain.Apps.Write.Contents
private void VerifyNotCreated() private void VerifyNotCreated()
{ {
if (isCreated) if (State.Data != null)
{ {
throw new DomainException("Content has already been created."); throw new DomainException("Content has already been created.");
} }
@ -139,15 +91,15 @@ namespace Squidex.Domain.Apps.Write.Contents
private void VerifyCreatedAndNotDeleted() private void VerifyCreatedAndNotDeleted()
{ {
if (isDeleted || !isCreated) if (State.IsDeleted || State.Data == null)
{ {
throw new DomainException("Content has already been deleted or not created yet."); throw new DomainException("Content has already been deleted or not created yet.");
} }
} }
protected override void DispatchEvent(Envelope<IEvent> @event) protected override void OnRaised(Envelope<IEvent> @event)
{ {
this.DispatchAction(@event.Payload); UpdateState(State.Apply(@event));
} }
} }
} }

4
src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentHistoryEventsCreator.cs

@ -7,12 +7,12 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Contents;
using Squidex.Domain.Apps.Read.History;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
namespace Squidex.Domain.Apps.Read.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentHistoryEventsCreator : HistoryEventsCreatorBase public sealed class ContentHistoryEventsCreator : HistoryEventsCreatorBase
{ {

42
src/Squidex.Domain.Apps.Write/Contents/ContentOperationContext.cs → 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.EnrichContent;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Read; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Domain.Apps.Write.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Tasks;
#pragma warning disable IDE0017 // Simplify object initialization namespace Squidex.Domain.Apps.Entities.Contents
namespace Squidex.Domain.Apps.Write.Contents
{ {
public sealed class ContentOperationContext public sealed class ContentOperationContext
{ {
@ -46,18 +43,19 @@ namespace Squidex.Domain.Apps.Write.Contents
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
Func<string> message) Func<string> message)
{ {
var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Name, command.SchemaId.Id); var (appEntity, schemaEntity) = await appProvider.GetAppWithSchemaAsync(command.AppId.Id, command.SchemaId.Id);
var context = new ContentOperationContext();
context.appEntity = appEntity; var context = new ContentOperationContext
context.assetRepository = assetRepository; {
context.contentRepository = contentRepository; appEntity = appEntity,
context.content = content; assetRepository = assetRepository,
context.command = command; contentRepository = contentRepository,
context.message = message; content = content,
context.schemaEntity = schemaEntity; command = command,
context.scriptEngine = scriptEngine; message = message,
schemaEntity = schemaEntity,
scriptEngine = scriptEngine
};
return context; return context;
} }
@ -121,7 +119,7 @@ namespace Squidex.Domain.Apps.Write.Contents
{ {
if (command is ContentDataCommand dataCommand) 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)); dataCommand.Data = scriptEngine.ExecuteAndTransform(ctx, script(schemaEntity));
} }
@ -131,7 +129,7 @@ namespace Squidex.Domain.Apps.Write.Contents
public Task ExecuteScriptAsync(Func<ISchemaEntity, string> script, object operation) public Task ExecuteScriptAsync(Func<ISchemaEntity, string> 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)); scriptEngine.Execute(ctx, script(schemaEntity));

21
src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs → src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -16,15 +16,15 @@ using Microsoft.OData.UriParser;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Read.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Read.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public sealed class ContentQueryService : IContentQueryService public sealed class ContentQueryService : IContentQueryService
{ {
@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Read.Contents
this.modelBuilder = modelBuilder; 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(app, nameof(app));
Guard.NotNull(user, nameof(user)); Guard.NotNull(user, nameof(user));
@ -60,7 +60,10 @@ namespace Squidex.Domain.Apps.Read.Contents
var schema = await FindSchemaAsync(app, schemaIdOrName); 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)) 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)) if (Guid.TryParse(schemaIdOrName, out var id))
{ {
schema = await appProvider.GetSchemaAsync(app.Name, id); schema = await appProvider.GetSchemaAsync(app.Id, id);
} }
if (schema == null) if (schema == null)
{ {
schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName); schema = await appProvider.GetSchemaAsync(app.Id, schemaIdOrName);
} }
if (schema == null) if (schema == null)

6
src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs → 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;
using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Contents.Edm namespace Squidex.Domain.Apps.Entities.Contents.Edm
{ {
public class EdmModelBuilder : CachingProviderBase public class EdmModelBuilder : CachingProviderBase
{ {

2
src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelExtensions.cs → src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelExtensions.cs

@ -11,7 +11,7 @@ using System.Linq;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
namespace Squidex.Domain.Apps.Read.Contents.Edm namespace Squidex.Domain.Apps.Entities.Contents.Edm
{ {
public static class EdmModelExtensions public static class EdmModelExtensions
{ {

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

Loading…
Cancel
Save