Browse Source

Merge pull request #194 from Squidex/orleans-learnings

Orleans learnings
pull/158/head
Sebastian Stehle 9 years ago
committed by GitHub
parent
commit
a242eefca1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .drone.yml
  2. 8
      Dockerfile
  3. 1
      NuGet.Config
  4. 33
      Squidex.sln
  5. 15
      Squidex.sln.DotSettings
  6. BIN
      libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.2.0.0-beta1-fix.nupkg
  7. 1
      libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.2.0.0-beta1-fix.nupkg.sha512
  8. 23
      libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.nuspec
  9. BIN
      libs/orleansdashboard/2.0.0-beta3/orleansdashboard.2.0.0-beta3.nupkg
  10. 1
      libs/orleansdashboard/2.0.0-beta3/orleansdashboard.2.0.0-beta3.nupkg.sha512
  11. 25
      libs/orleansdashboard/2.0.0-beta3/orleansdashboard.nuspec
  12. BIN
      nuget.exe
  13. 15
      src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs
  14. 57
      src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs
  15. 26
      src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs
  16. 13
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs
  17. 13
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs
  18. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs
  19. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs
  20. 113
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  21. 12
      src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs
  22. 31
      src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs
  23. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs
  24. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs
  25. 9
      src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs
  26. 35
      src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  27. 4
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs
  28. 4
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs
  29. 18
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs
  30. 36
      src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  31. 36
      src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs
  32. 81
      src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  33. 45
      src/Squidex.Domain.Apps.Core.Model/Schemas/Field.cs
  34. 48
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  35. 84
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs
  36. 14
      src/Squidex.Domain.Apps.Core.Model/Schemas/Field{T}.cs
  37. 21
      src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs
  38. 30
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs
  39. 3
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs
  40. 5
      src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs
  41. 35
      src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs
  42. 82
      src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  43. 51
      src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  44. 208
      src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs
  45. 112
      src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  46. 36
      src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs
  47. 4
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  48. 2
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs
  49. 6
      src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  50. 6
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  51. 46
      src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs
  52. 16
      src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs
  53. 83
      src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs
  54. 4
      src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj
  55. 59
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs
  56. 70
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs
  57. 143
      src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs
  58. 7
      src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs
  59. 6
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs
  60. 15
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs
  61. 4
      src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  62. 7
      src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs
  63. 6
      src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs
  64. 21
      src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs
  65. 25
      src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs
  66. 4
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs
  67. 10
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs
  68. 90
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs
  69. 97
      src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs
  70. 73
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs
  71. 188
      src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs
  72. 2
      src/Squidex.Domain.Apps.Read.MongoDb/Squidex.Domain.Apps.Read.MongoDb.csproj
  73. 14
      src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs
  74. 5
      src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs
  75. 23
      src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs
  76. 121
      src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs
  77. 2
      src/Squidex.Domain.Apps.Read/Assets/IAssetEntity.cs
  78. 11
      src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs
  79. 13
      src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs
  80. 2
      src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs
  81. 49
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs
  82. 2
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs
  83. 4
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs
  84. 2
      src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs
  85. 34
      src/Squidex.Domain.Apps.Read/EntityMapper.cs
  86. 34
      src/Squidex.Domain.Apps.Read/IAppProvider.cs
  87. 6
      src/Squidex.Domain.Apps.Read/IEntity.cs
  88. 17
      src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs
  89. 2
      src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs
  90. 2
      src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs
  91. 6
      src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs
  92. 9
      src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs
  93. 17
      src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs
  94. 15
      src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs
  95. 4
      src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs
  96. 2
      src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs
  97. 114
      src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs
  98. 15
      src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs
  99. 2
      src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs
  100. 23
      src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs

2
.drone.yml

@ -21,7 +21,7 @@ pipeline:
secrets: [ docker_username, docker_password ] secrets: [ docker_username, docker_password ]
when: when:
event: push event: push
branch: [master] branch: [ master ]
build_release: build_release:
image: docker image: docker

8
Dockerfile

@ -1,7 +1,7 @@
# #
# Stage 1, Prebuild # Stage 1, Prebuild
# #
FROM microsoft/aspnetcore-build:2.0.0-jessie as builder FROM microsoft/aspnetcore-build:2.0.3-jessie as builder
# Install runtime dependencies # Install runtime dependencies
RUN apt-get update \ RUN apt-get update \
@ -45,7 +45,7 @@ RUN cp -a /tmp/node_modules /src/Squidex/ \
# Test Backend # Test Backend
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 -v d \
&& 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.Read.Tests/Squidex.Domain.Apps.Read.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \ && dotnet test tests/Squidex.Domain.Apps.Write.Tests/Squidex.Domain.Apps.Write.Tests.csproj \
@ -57,7 +57,7 @@ RUN dotnet publish src/Squidex/Squidex.csproj --output /out/ --configuration Rel
# #
# Stage 2, Build runtime # Stage 2, Build runtime
# #
FROM microsoft/aspnetcore:2.0.0-jessie FROM microsoft/aspnetcore:2.0.3-jessie
# Default AspNetCore directory # Default AspNetCore directory
WORKDIR /app WORKDIR /app
@ -66,5 +66,7 @@ WORKDIR /app
COPY --from=builder /out/ . COPY --from=builder /out/ .
EXPOSE 80 EXPOSE 80
EXPOSE 33333
EXPOSE 40000
ENTRYPOINT ["dotnet", "Squidex.dll"] ENTRYPOINT ["dotnet", "Squidex.dll"]

1
NuGet.Config

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="localfeed" value="libs" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration> </configuration>

33
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.2002 VisualStudioVersion = 15.0.27004.2009
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
@ -34,10 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.Rabb
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.GoogleCloud", "src\Squidex.Infrastructure.GoogleCloud\Squidex.Infrastructure.GoogleCloud.csproj", "{945871B1-77B8-43FB-B53C-27CF385AB756}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Squidex.Infrastructure.GoogleCloud", "src\Squidex.Infrastructure.GoogleCloud\Squidex.Infrastructure.GoogleCloud.csproj", "{945871B1-77B8-43FB-B53C-27CF385AB756}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{B56EBCEC-9C50-46A7-848C-65502DE69C5C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "tests\Benchmarks\Benchmarks.csproj", "{D48A03DF-BCD3-4667-8747-2F251347E2B6}"
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_01", "tools\Migrate_01\Migrate_01.csproj", "{B51126A8-0D75-4A79-867D-10724EC6AC84}"
@ -67,6 +63,8 @@ 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}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -197,18 +195,6 @@ Global
{945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x64.Build.0 = Release|Any CPU {945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x64.Build.0 = Release|Any CPU
{945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x86.ActiveCfg = Release|Any CPU {945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x86.ActiveCfg = Release|Any CPU
{945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x86.Build.0 = Release|Any CPU {945871B1-77B8-43FB-B53C-27CF385AB756}.Release|x86.Build.0 = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|x64.ActiveCfg = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|x64.Build.0 = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|x86.ActiveCfg = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Debug|x86.Build.0 = Debug|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|Any CPU.Build.0 = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|x64.ActiveCfg = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|x64.Build.0 = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|x86.ActiveCfg = Release|Any CPU
{D48A03DF-BCD3-4667-8747-2F251347E2B6}.Release|x86.Build.0 = Release|Any CPU
{B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|Any CPU.Build.0 = Debug|Any CPU {B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|x64.ActiveCfg = Debug|Any CPU {B51126A8-0D75-4A79-867D-10724EC6AC84}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -317,6 +303,18 @@ 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
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x64.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.ActiveCfg = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Debug|x86.Build.0 = Debug|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x64.Build.0 = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.ActiveCfg = Release|Any CPU
{9B4A55F4-D9A4-4FC3-8D85-02A9EF93FBAB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -335,7 +333,6 @@ Global
{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}
{D48A03DF-BCD3-4667-8747-2F251347E2B6} = {B56EBCEC-9C50-46A7-848C-65502DE69C5C}
{B51126A8-0D75-4A79-867D-10724EC6AC84} = {94207AA6-4923-4183-A558-E0F8196B8CA3} {B51126A8-0D75-4A79-867D-10724EC6AC84} = {94207AA6-4923-4183-A558-E0F8196B8CA3}
{5E75AB7D-6F01-4313-AFF1-7F7128FFD71F} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {5E75AB7D-6F01-4313-AFF1-7F7128FFD71F} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}
{C9809D59-6665-471E-AD87-5AC624C65892} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A} {C9809D59-6665-471E-AD87-5AC624C65892} = {4C6B06C2-6D77-4E0E-AE32-D7050236433A}

15
Squidex.sln.DotSettings

@ -5,12 +5,25 @@
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecshtml/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecshtml/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecss/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecss/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecss/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ecss/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ehtml/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ehtml/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejs/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejs/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejs/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejs/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejson/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ejson/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Escss/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Escss/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Escss/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Escss/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ets/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002E_002A/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002E_002A/@EntryIndexedValue">True</s:Boolean>
@ -18,6 +31,8 @@
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Header/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Header"&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Header/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Header"&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Namespaces/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Namespaces"&gt;&lt;CSOptimizeUsings&gt;&lt;OptimizeUsings&gt;True&lt;/OptimizeUsings&gt;&lt;EmbraceInRegion&gt;False&lt;/EmbraceInRegion&gt;&lt;RegionName&gt;&lt;/RegionName&gt;&lt;/CSOptimizeUsings&gt;&lt;CSUpdateFileHeader&gt;True&lt;/CSUpdateFileHeader&gt;&lt;/Profile&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String> <s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Typescript/@EntryIndexedValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;Profile name="Typescript"&gt;&lt;JsInsertSemicolon&gt;True&lt;/JsInsertSemicolon&gt;&lt;FormatAttributeQuoteDescriptor&gt;True&lt;/FormatAttributeQuoteDescriptor&gt;&lt;CorrectVariableKindsDescriptor&gt;True&lt;/CorrectVariableKindsDescriptor&gt;&lt;VariablesToInnerScopesDescriptor&gt;True&lt;/VariablesToInnerScopesDescriptor&gt;&lt;StringToTemplatesDescriptor&gt;True&lt;/StringToTemplatesDescriptor&gt;&lt;RemoveRedundantQualifiersTs&gt;True&lt;/RemoveRedundantQualifiersTs&gt;&lt;OptimizeImportsTs&gt;True&lt;/OptimizeImportsTs&gt;&lt;/Profile&gt;</s:String>

BIN
libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.2.0.0-beta1-fix.nupkg

Binary file not shown.

1
libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.2.0.0-beta1-fix.nupkg.sha512

@ -0,0 +1 @@
5W20j9jiNog4dHUEt+cCnePb8z6jFEMnkwO4XilajM7FCnen3KTnN/G8PAUGuQieSlTI9MRe0sRYcafLJl900w==

23
libs/microsoft.orleans.orleanscodegenerator.build/2.0.0-beta1-fix/microsoft.orleans.orleanscodegenerator.build.nuspec

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Microsoft.Orleans.OrleansCodeGenerator.Build</id>
<version>2.0.0-beta1-fix</version>
<title>Microsoft Orleans Build-time Code Generator</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<developmentDependency>true</developmentDependency>
<licenseUrl>https://github.com/dotnet/Orleans#license</licenseUrl>
<projectUrl>https://github.com/dotnet/Orleans</projectUrl>
<iconUrl>https://raw.githubusercontent.com/dotnet/orleans/gh-pages/assets/logo_128.png</iconUrl>
<description>Microsoft Orleans build-time code generator to install in all grain interface &amp; implementation projects.</description>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<tags>Orleans Cloud-Computing Actor-Model Actors Distributed-Systems C# .NET</tags>
<repository type="git" url="https://github.com/dotnet/Orleans" />
<dependencies>
<group targetFramework=".NETFramework4.6.1" />
<group targetFramework=".NETCoreApp2.0" />
</dependencies>
</metadata>
</package>

BIN
libs/orleansdashboard/2.0.0-beta3/orleansdashboard.2.0.0-beta3.nupkg

Binary file not shown.

1
libs/orleansdashboard/2.0.0-beta3/orleansdashboard.2.0.0-beta3.nupkg.sha512

@ -0,0 +1 @@
mBHlGWl+bNTPP463JBEB/dftmdZKQRD8X72F7lsTFqYWddW5Ytp1gbzChCxW0d/Pt71KLF6XrVmyecbFlNdFBA==

25
libs/orleansdashboard/2.0.0-beta3/orleansdashboard.nuspec

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>OrleansDashboard</id>
<version>2.0.0-beta3</version>
<authors>OrleansContrib</authors>
<owners>OrleansContrib</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<projectUrl>https://github.com/OrleansContrib/OrleansDashboard</projectUrl>
<iconUrl>http://dotnet.github.io/orleans/assets/logo.png</iconUrl>
<description>An admin dashboard for Microsoft Orleans</description>
<copyright>Copyright © 2017</copyright>
<tags>orleans dashboard metrics monitor</tags>
<repository url="https://github.com/OrleansContrib/OrleansDashboard" />
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.AspNetCore" version="2.0.1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.Core" version="2.0.0-beta1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.OrleansCodeGenerator.Build" version="2.0.0-beta1" exclude="Build,Analyzers" />
<dependency id="Microsoft.Orleans.OrleansRuntime" version="2.0.0-beta1" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>

BIN
nuget.exe

Binary file not shown.

15
src/Squidex.Domain.Apps.Core.Model/Apps/AppClient.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
@ -13,8 +14,8 @@ namespace Squidex.Domain.Apps.Core.Apps
public sealed class AppClient public sealed class AppClient
{ {
private readonly string secret; private readonly string secret;
private string name; private readonly string name;
private AppClientPermission permission; private readonly AppClientPermission permission;
public string Name public string Name
{ {
@ -42,18 +43,20 @@ namespace Squidex.Domain.Apps.Core.Apps
this.permission = permission; this.permission = permission;
} }
public void Update(AppClientPermission newPermission) [Pure]
public AppClient Update(AppClientPermission newPermission)
{ {
Guard.Enum(newPermission, nameof(newPermission)); Guard.Enum(newPermission, nameof(newPermission));
permission = newPermission; return new AppClient(name, secret, newPermission);
} }
public void Rename(string newName) [Pure]
public AppClient Rename(string newName)
{ {
Guard.NotNullOrEmpty(newName, nameof(newName)); Guard.NotNullOrEmpty(newName, nameof(newName));
name = newName; return new AppClient(newName, secret, permission);
} }
} }
} }

57
src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs

@ -6,32 +6,75 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppClients : DictionaryBase<string, AppClient> public sealed class AppClients : DictionaryWrapper<string, AppClient>
{ {
public void Add(string id, AppClient client) public static readonly AppClients Empty = new AppClients();
private AppClients()
: base(ImmutableDictionary<string, AppClient>.Empty)
{
}
public AppClients(ImmutableDictionary<string, AppClient> inner)
: base(inner)
{
}
[Pure]
public AppClients Add(string id, AppClient client)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(id, nameof(id));
Guard.NotNull(client, nameof(client)); Guard.NotNull(client, nameof(client));
Inner.Add(id, client); return new AppClients(Inner.Add(id, client));
} }
public void Add(string id, string secret) [Pure]
public AppClients Add(string id, string secret)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(id, nameof(id));
Inner.Add(id, new AppClient(id, secret, AppClientPermission.Editor)); return new AppClients(Inner.Add(id, new AppClient(id, secret, AppClientPermission.Editor)));
} }
public void Revoke(string id) [Pure]
public AppClients Revoke(string id)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(id, nameof(id));
Inner.Remove(id); return new AppClients(Inner.Remove(id));
}
[Pure]
public AppClients Rename(string id, string newName)
{
Guard.NotNullOrEmpty(id, nameof(id));
if (!TryGetValue(id, out var client))
{
return this;
}
return new AppClients(Inner.SetItem(id, client.Rename(newName)));
}
[Pure]
public AppClients Update(string id, AppClientPermission permission)
{
Guard.NotNullOrEmpty(id, nameof(id));
if (!TryGetValue(id, out var client))
{
return this;
}
return new AppClients(Inner.SetItem(id, client.Update(permission)));
} }
} }
} }

26
src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs

@ -6,25 +6,41 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppContributors : DictionaryBase<string, AppContributorPermission> public sealed class AppContributors : DictionaryWrapper<string, AppContributorPermission>
{ {
public void Assign(string contributorId, AppContributorPermission permission) public static readonly AppContributors Empty = new AppContributors();
private AppContributors()
: base(ImmutableDictionary<string, AppContributorPermission>.Empty)
{
}
public AppContributors(ImmutableDictionary<string, AppContributorPermission> inner)
: base(inner)
{
}
[Pure]
public AppContributors Assign(string contributorId, AppContributorPermission permission)
{ {
Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.NotNullOrEmpty(contributorId, nameof(contributorId));
Guard.Enum(permission, nameof(permission)); Guard.Enum(permission, nameof(permission));
Inner[contributorId] = permission; return new AppContributors(Inner.SetItem(contributorId, permission));
} }
public void Remove(string contributorId) [Pure]
public AppContributors Remove(string contributorId)
{ {
Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.NotNullOrEmpty(contributorId, nameof(contributorId));
Inner.Remove(contributorId); return new AppContributors(Inner.Remove(contributorId));
} }
} }
} }

13
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs

@ -6,7 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
@ -26,18 +28,11 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
serializer.Serialize(writer, json); serializer.Serialize(writer, json);
} }
protected override AppClients ReadValue(JsonReader reader, JsonSerializer serializer) protected override AppClients ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<Dictionary<string, JsonAppClient>>(reader); var json = serializer.Deserialize<Dictionary<string, JsonAppClient>>(reader);
var clients = new AppClients(); return new AppClients(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToClient()));
foreach (var client in json)
{
clients.Add(client.Key, client.Value.ToClient());
}
return clients;
} }
} }
} }

13
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs

@ -6,7 +6,9 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
@ -26,18 +28,11 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
serializer.Serialize(writer, json); serializer.Serialize(writer, json);
} }
protected override AppContributors ReadValue(JsonReader reader, JsonSerializer serializer) protected override AppContributors ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<Dictionary<string, AppContributorPermission>>(reader); var json = serializer.Deserialize<Dictionary<string, AppContributorPermission>>(reader);
var contributors = new AppContributors(); return new AppContributors(json.ToImmutableDictionary());
foreach (var contributor in json)
{
contributors.Assign(contributor.Key, contributor.Value);
}
return contributors;
} }
} }
} }

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/JsonLanguagesConfig.cs

@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
if (Master != null) if (Master != null)
{ {
result.MakeMaster(Master); result = result.MakeMaster(Master);
} }
return result; return result;

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
serializer.Serialize(writer, json); serializer.Serialize(writer, json);
} }
protected override LanguagesConfig ReadValue(JsonReader reader, JsonSerializer serializer) protected override LanguagesConfig ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
var json = serializer.Deserialize<JsonLanguagesConfig>(reader); var json = serializer.Deserialize<JsonLanguagesConfig>(reader);

113
src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs

@ -10,83 +10,108 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
#pragma warning disable IDE0016 // Use 'throw' expression
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class LanguagesConfig : IFieldPartitioning public sealed class LanguagesConfig : IFieldPartitioning
{ {
private State state; public static readonly LanguagesConfig Empty = new LanguagesConfig(ImmutableDictionary<Language, LanguageConfig>.Empty, null, false);
public static readonly LanguagesConfig English = LanguagesConfig.Build(Language.EN);
private readonly ImmutableDictionary<Language, LanguageConfig> languages;
private readonly LanguageConfig master;
public LanguageConfig Master public LanguageConfig Master
{ {
get { return state.Master; } get { return master; }
} }
IFieldPartitionItem IFieldPartitioning.Master IFieldPartitionItem IFieldPartitioning.Master
{ {
get { return state.Master; } get { return master; }
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return state.Languages.Values.GetEnumerator(); return languages.Values.GetEnumerator();
} }
IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator() IEnumerator<IFieldPartitionItem> IEnumerable<IFieldPartitionItem>.GetEnumerator()
{ {
return state.Languages.Values.GetEnumerator(); return languages.Values.GetEnumerator();
} }
public int Count public int Count
{ {
get { return state.Languages.Count; } get { return languages.Count; }
} }
private LanguagesConfig(ICollection<LanguageConfig> configs) private LanguagesConfig(ImmutableDictionary<Language, LanguageConfig> languages, LanguageConfig master, bool checkMaster = true)
{ {
Guard.NotNull(configs, nameof(configs)); if (checkMaster)
{
this.master = master ?? throw new InvalidOperationException("Config has no master language.");
}
foreach (var languageConfig in languages.Values)
{
foreach (var fallback in languageConfig.LanguageFallbacks)
{
if (!languages.ContainsKey(fallback))
{
var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'";
state = new State(configs.ToImmutableDictionary(x => x.Language), configs.FirstOrDefault()); throw new InvalidOperationException(message);
}
}
}
this.languages = languages;
} }
public static LanguagesConfig Build(params LanguageConfig[] configs) public static LanguagesConfig Build(ICollection<LanguageConfig> configs)
{ {
Guard.NotNull(configs, nameof(configs)); Guard.NotNull(configs, nameof(configs));
return new LanguagesConfig(configs); return new LanguagesConfig(configs.ToImmutableDictionary(x => x.Language), configs.FirstOrDefault());
} }
public static LanguagesConfig Build(params Language[] languages) public static LanguagesConfig Build(params LanguageConfig[] configs)
{ {
Guard.NotNull(languages, nameof(languages)); return Build(configs?.ToList());
}
return new LanguagesConfig(languages.Select(x => new LanguageConfig(x, false)).ToList()); public static LanguagesConfig Build(params Language[] languages)
{
return Build(languages?.Select(x => new LanguageConfig(x)).ToList());
} }
public void MakeMaster(Language language) [Pure]
public LanguagesConfig MakeMaster(Language language)
{ {
Guard.NotNull(language, nameof(language)); Guard.NotNull(language, nameof(language));
state = new State(state.Languages, state.Languages[language]); return new LanguagesConfig(languages, languages[language]);
} }
public void Set(LanguageConfig config) [Pure]
public LanguagesConfig Set(LanguageConfig config)
{ {
Guard.NotNull(config, nameof(config)); Guard.NotNull(config, nameof(config));
state = new State(state.Languages.SetItem(config.Language, config), state.Master?.Language == config.Language ? config : state.Master); return new LanguagesConfig(languages.SetItem(config.Language, config), Master?.Language == config.Language ? config : Master);
} }
public void Remove(Language language) [Pure]
public LanguagesConfig Remove(Language language)
{ {
Guard.NotNull(language, nameof(language)); Guard.NotNull(language, nameof(language));
var newLanguages = var newLanguages =
state.Languages.Values.Where(x => x.Language != language) languages.Values.Where(x => x.Language != language)
.Select(config => .Select(config =>
{ {
return new LanguageConfig( return new LanguageConfig(
@ -97,26 +122,26 @@ namespace Squidex.Domain.Apps.Core.Apps
.ToImmutableDictionary(x => x.Language); .ToImmutableDictionary(x => x.Language);
var newMaster = var newMaster =
state.Master.Language != language ? Master.Language != language ?
state.Master : Master :
newLanguages.Values.FirstOrDefault(); newLanguages.Values.FirstOrDefault();
state = new State(newLanguages, newMaster); return new LanguagesConfig(newLanguages, newMaster);
} }
public bool Contains(Language language) public bool Contains(Language language)
{ {
return language != null && state.Languages.ContainsKey(language); return language != null && languages.ContainsKey(language);
} }
public bool TryGetConfig(Language language, out LanguageConfig config) public bool TryGetConfig(Language language, out LanguageConfig config)
{ {
return state.Languages.TryGetValue(language, out config); return languages.TryGetValue(language, out config);
} }
public bool TryGetItem(string key, out IFieldPartitionItem item) public bool TryGetItem(string key, out IFieldPartitionItem item)
{ {
if (Language.IsValidLanguage(key) && state.Languages.TryGetValue(key, out var value)) if (Language.IsValidLanguage(key) && languages.TryGetValue(key, out var value))
{ {
item = value; item = value;
@ -130,38 +155,6 @@ namespace Squidex.Domain.Apps.Core.Apps
} }
} }
private sealed class State
{
public ImmutableDictionary<Language, LanguageConfig> Languages { get; }
public LanguageConfig Master { get; }
public State(ImmutableDictionary<Language, LanguageConfig> languages, LanguageConfig master)
{
foreach (var languageConfig in languages.Values)
{
foreach (var fallback in languageConfig.LanguageFallbacks)
{
if (!languages.ContainsKey(fallback))
{
var message = $"Config for language '{languageConfig.Language.Iso2Code}' contains unsupported fallback language '{fallback.Iso2Code}'";
throw new InvalidOperationException(message);
}
}
}
Languages = languages;
if (master == null)
{
throw new InvalidOperationException("Config has no master language.");
}
this.Master = master;
}
}
public PartitionResolver ToResolver() public PartitionResolver ToResolver()
{ {
return partitioning => return partitioning =>

12
src/Squidex.Domain.Apps.Core.Model/DictionaryBase.cs → src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs

@ -8,12 +8,13 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
namespace Squidex.Domain.Apps.Core namespace Squidex.Domain.Apps.Core
{ {
public abstract class DictionaryBase<TKey, TValue> : IReadOnlyDictionary<TKey, TValue> public abstract class DictionaryWrapper<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{ {
private readonly Dictionary<TKey, TValue> inner = new Dictionary<TKey, TValue>(); private readonly ImmutableDictionary<TKey, TValue> inner;
public TValue this[TKey key] public TValue this[TKey key]
{ {
@ -35,11 +36,16 @@ namespace Squidex.Domain.Apps.Core
get { return inner.Count; } get { return inner.Count; }
} }
protected Dictionary<TKey, TValue> Inner protected ImmutableDictionary<TKey, TValue> Inner
{ {
get { return inner; } get { return inner; }
} }
protected DictionaryWrapper(ImmutableDictionary<TKey, TValue> inner)
{
this.inner = inner;
}
public bool ContainsKey(TKey key) public bool ContainsKey(TKey key)
{ {
return inner.ContainsKey(key); return inner.ContainsKey(key);

31
src/Squidex.Domain.Apps.Core.Model/Rules/Actions/WebhookAction.cs

@ -14,9 +14,36 @@ namespace Squidex.Domain.Apps.Core.Rules.Actions
[TypeName(nameof(WebhookAction))] [TypeName(nameof(WebhookAction))]
public sealed class WebhookAction : RuleAction public sealed class WebhookAction : RuleAction
{ {
public Uri Url { get; set; } private Uri url;
private string sharedSecret;
public string SharedSecret { get; set; } public Uri Url
{
get
{
return url;
}
set
{
ThrowIfFrozen();
url = value;
}
}
public string SharedSecret
{
get
{
return sharedSecret;
}
set
{
ThrowIfFrozen();
sharedSecret = value;
}
}
public override T Accept<T>(IRuleActionVisitor<T> visitor) public override T Accept<T>(IRuleActionVisitor<T> visitor)
{ {

2
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleActionVisitor.cs

@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Core.Rules.Actions;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public interface IRuleActionVisitor<T> public interface IRuleActionVisitor<out T>
{ {
T Visit(WebhookAction action); T Visit(WebhookAction action);
} }

2
src/Squidex.Domain.Apps.Core.Model/Rules/IRuleTriggerVisitor.cs

@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Core.Rules.Triggers;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public interface IRuleTriggerVisitor<T> public interface IRuleTriggerVisitor<out T>
{ {
T Visit(ContentChangedTrigger trigger); T Visit(ContentChangedTrigger trigger);
} }

9
src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
@ -13,14 +14,14 @@ namespace Squidex.Domain.Apps.Core.Rules.Json
{ {
public sealed class RuleConverter : JsonClassConverter<Rule> public sealed class RuleConverter : JsonClassConverter<Rule>
{ {
protected override Rule ReadValue(JsonReader reader, JsonSerializer serializer) protected override void WriteValue(JsonWriter writer, Rule value, JsonSerializer serializer)
{ {
return serializer.Deserialize<JsonRule>(reader).ToRule(); serializer.Serialize(writer, new JsonRule(value));
} }
protected override void WriteValue(JsonWriter writer, Rule value, JsonSerializer serializer) protected override Rule ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
serializer.Serialize(writer, new JsonRule(value)); return serializer.Deserialize<JsonRule>(reader).ToRule();
} }
} }
} }

35
src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs

@ -7,11 +7,12 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public sealed class Rule public sealed class Rule : Cloneable<Rule>
{ {
private RuleTrigger trigger; private RuleTrigger trigger;
private RuleAction action; private RuleAction action;
@ -41,17 +42,26 @@ namespace Squidex.Domain.Apps.Core.Rules
this.action = action; this.action = action;
} }
public void Enable() [Pure]
public Rule Enable()
{ {
this.isEnabled = true; return Clone(clone =>
{
clone.isEnabled = true;
});
} }
public void Disable() [Pure]
public Rule Disable()
{ {
this.isEnabled = false; return Clone(clone =>
{
clone.isEnabled = false;
});
} }
public void Update(RuleTrigger newTrigger) [Pure]
public Rule Update(RuleTrigger newTrigger)
{ {
Guard.NotNull(newTrigger, nameof(newTrigger)); Guard.NotNull(newTrigger, nameof(newTrigger));
@ -60,10 +70,14 @@ namespace Squidex.Domain.Apps.Core.Rules
throw new ArgumentException("New trigger has another type.", nameof(newTrigger)); throw new ArgumentException("New trigger has another type.", nameof(newTrigger));
} }
trigger = newTrigger; return Clone(clone =>
{
clone.trigger = newTrigger;
});
} }
public void Update(RuleAction newAction) [Pure]
public Rule Update(RuleAction newAction)
{ {
Guard.NotNull(newAction, nameof(newAction)); Guard.NotNull(newAction, nameof(newAction));
@ -72,7 +86,10 @@ namespace Squidex.Domain.Apps.Core.Rules
throw new ArgumentException("New action has another type.", nameof(newAction)); throw new ArgumentException("New action has another type.", nameof(newAction));
} }
action = newAction; return Clone(clone =>
{
clone.action = newAction;
});
} }
} }
} }

4
src/Squidex.Domain.Apps.Core.Model/Rules/RuleAction.cs

@ -6,9 +6,11 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public abstract class RuleAction public abstract class RuleAction : Freezable
{ {
public abstract T Accept<T>(IRuleActionVisitor<T> visitor); public abstract T Accept<T>(IRuleActionVisitor<T> visitor);
} }

4
src/Squidex.Domain.Apps.Core.Model/Rules/RuleTrigger.cs

@ -6,9 +6,11 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
{ {
public abstract class RuleTrigger public abstract class RuleTrigger : Freezable
{ {
public abstract T Accept<T>(IRuleTriggerVisitor<T> visitor); public abstract T Accept<T>(IRuleTriggerVisitor<T> visitor);
} }

18
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs

@ -6,7 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.Triggers namespace Squidex.Domain.Apps.Core.Rules.Triggers
@ -14,7 +14,21 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
[TypeName(nameof(ContentChangedTrigger))] [TypeName(nameof(ContentChangedTrigger))]
public sealed class ContentChangedTrigger : RuleTrigger public sealed class ContentChangedTrigger : RuleTrigger
{ {
public List<ContentChangedTriggerSchema> Schemas { get; set; } private ImmutableList<ContentChangedTriggerSchema> schemas;
public ImmutableList<ContentChangedTriggerSchema> Schemas
{
get
{
return schemas;
}
set
{
ThrowIfFrozen();
schemas = value;
}
}
public override T Accept<T>(IRuleTriggerVisitor<T> visitor) public override T Accept<T>(IRuleTriggerVisitor<T> visitor)
{ {

36
src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -13,13 +13,45 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(AssetsField))] [TypeName(nameof(AssetsField))]
public sealed class AssetsFieldProperties : FieldProperties public sealed class AssetsFieldProperties : FieldProperties
{ {
public int? MinItems { get; set; } private int? minItems;
private int? maxItems;
public int? MaxItems { get; set; } public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new AssetsField(id, name, partitioning, this);
}
} }
} }

36
src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs

@ -13,13 +13,45 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(BooleanField))] [TypeName(nameof(BooleanField))]
public sealed class BooleanFieldProperties : FieldProperties public sealed class BooleanFieldProperties : FieldProperties
{ {
public bool? DefaultValue { get; set; } private BooleanFieldEditor editor;
private bool? defaultValue;
public BooleanFieldEditor Editor { get; set; } public bool? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
defaultValue = value;
}
}
public BooleanFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new BooleanField(id, name, partitioning, this);
}
} }
} }

81
src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs

@ -14,19 +14,90 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(DateTimeField))] [TypeName(nameof(DateTimeField))]
public sealed class DateTimeFieldProperties : FieldProperties public sealed class DateTimeFieldProperties : FieldProperties
{ {
public Instant? MaxValue { get; set; } private DateTimeFieldEditor editor;
private DateTimeCalculatedDefaultValue? calculatedDefaultValue;
private Instant? maxValue;
private Instant? minValue;
private Instant? defaultValue;
public Instant? MinValue { get; set; } public Instant? MaxValue
{
get
{
return maxValue;
}
set
{
ThrowIfFrozen();
maxValue = value;
}
}
public Instant? MinValue
{
get
{
return minValue;
}
set
{
ThrowIfFrozen();
public Instant? DefaultValue { get; set; } minValue = value;
}
}
public DateTimeFieldEditor Editor { get; set; } public Instant? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; set; } defaultValue = value;
}
}
public DateTimeCalculatedDefaultValue? CalculatedDefaultValue
{
get
{
return calculatedDefaultValue;
}
set
{
ThrowIfFrozen();
calculatedDefaultValue = value;
}
}
public DateTimeFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new DateTimeField(id, name, partitioning, this);
}
} }
} }

45
src/Squidex.Domain.Apps.Core.Model/Schemas/Field.cs

@ -6,11 +6,12 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public abstract class Field public abstract class Field : Cloneable<Field>
{ {
private readonly long fieldId; private readonly long fieldId;
private readonly Partitioning partitioning; private readonly Partitioning partitioning;
@ -63,32 +64,52 @@ namespace Squidex.Domain.Apps.Core.Schemas
this.partitioning = partitioning; this.partitioning = partitioning;
} }
public void Lock() [Pure]
public Field Lock()
{ {
isLocked = true; return Clone<Field>(clone =>
{
clone.isLocked = true;
});
} }
public void Hide() [Pure]
public Field Hide()
{ {
isHidden = true; return Clone(clone =>
{
clone.isHidden = true;
});
} }
public void Show() [Pure]
public Field Show()
{ {
isHidden = false; return Clone(clone =>
{
clone.isHidden = false;
});
} }
public void Disable() [Pure]
public Field Disable()
{ {
isDisabled = true; return Clone(clone =>
{
clone.isDisabled = true;
});
} }
public void Enable() [Pure]
public Field Enable()
{ {
isDisabled = false; return Clone(clone =>
{
clone.isDisabled = false;
});
} }
public abstract void Update(FieldProperties newProperties); public abstract Field Update(FieldProperties newProperties);
public abstract T Accept<T>(IFieldVisitor<T> visitor); public abstract T Accept<T>(IFieldVisitor<T> visitor);
} }

48
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs

@ -10,12 +10,54 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public abstract class FieldProperties : NamedElementPropertiesBase public abstract class FieldProperties : NamedElementPropertiesBase
{ {
public bool IsRequired { get; set; } private bool isRequired;
private bool isListField;
private string placeholder;
public bool IsListField { get; set; } public bool IsRequired
{
get
{
return isRequired;
}
set
{
ThrowIfFrozen();
public string Placeholder { get; set; } isRequired = value;
}
}
public bool IsListField
{
get
{
return isListField;
}
set
{
ThrowIfFrozen();
isListField = value;
}
}
public string Placeholder
{
get
{
return placeholder;
}
set
{
ThrowIfFrozen();
placeholder = value;
}
}
public abstract T Accept<T>(IFieldPropertiesVisitor<T> visitor); public abstract T Accept<T>(IFieldPropertiesVisitor<T> visitor);
public abstract Field CreateField(long id, string name, Partitioning partitioning);
} }
} }

84
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs

@ -17,29 +17,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
private delegate Field FactoryFunction(long id, string name, Partitioning partitioning, FieldProperties properties); private delegate Field FactoryFunction(long id, string name, Partitioning partitioning, FieldProperties properties);
private readonly TypeNameRegistry typeNameRegistry; private readonly TypeNameRegistry typeNameRegistry;
private readonly Dictionary<Type, Registered> fieldsByPropertyType = new Dictionary<Type, Registered>(); private readonly Dictionary<Type, FactoryFunction> fieldsByPropertyType = new Dictionary<Type, FactoryFunction>();
private sealed class Registered
{
private readonly FactoryFunction fieldFactory;
private readonly Type propertiesType;
public Type PropertiesType
{
get { return propertiesType; }
}
public Registered(FactoryFunction fieldFactory, Type propertiesType)
{
this.fieldFactory = fieldFactory;
this.propertiesType = propertiesType;
}
public Field CreateField(long id, string name, Partitioning partitioning, FieldProperties properties)
{
return fieldFactory(id, name, partitioning, properties);
}
}
public FieldRegistry(TypeNameRegistry typeNameRegistry) public FieldRegistry(TypeNameRegistry typeNameRegistry)
{ {
@ -47,69 +25,39 @@ namespace Squidex.Domain.Apps.Core.Schemas
this.typeNameRegistry = typeNameRegistry; this.typeNameRegistry = typeNameRegistry;
Add<BooleanFieldProperties>( RegisterField<AssetsFieldProperties>();
(id, name, partitioning, properties) => RegisterField<BooleanFieldProperties>();
new BooleanField(id, name, partitioning, (BooleanFieldProperties)properties)); RegisterField<DateTimeFieldProperties>();
RegisterField<GeolocationFieldProperties>();
Add<NumberFieldProperties>( RegisterField<JsonFieldProperties>();
(id, name, partitioning, properties) => RegisterField<NumberFieldProperties>();
new NumberField(id, name, partitioning, (NumberFieldProperties)properties)); RegisterField<ReferencesFieldProperties>();
RegisterField<StringFieldProperties>();
Add<StringFieldProperties>( RegisterField<TagsFieldProperties>();
(id, name, partitioning, properties) =>
new StringField(id, name, partitioning, (StringFieldProperties)properties));
Add<JsonFieldProperties>(
(id, name, partitioning, properties) =>
new JsonField(id, name, partitioning, (JsonFieldProperties)properties));
Add<AssetsFieldProperties>(
(id, name, partitioning, properties) =>
new AssetsField(id, name, partitioning, (AssetsFieldProperties)properties));
Add<GeolocationFieldProperties>(
(id, name, partitioning, properties) =>
new GeolocationField(id, name, partitioning, (GeolocationFieldProperties)properties));
Add<ReferencesFieldProperties>(
(id, name, partitioning, properties) =>
new ReferencesField(id, name, partitioning, (ReferencesFieldProperties)properties));
Add<DateTimeFieldProperties>(
(id, name, partitioning, properties) =>
new DateTimeField(id, name, partitioning, (DateTimeFieldProperties)properties));
Add<TagsFieldProperties>(
(id, name, partitioning, properties) =>
new TagsField(id, name, partitioning, (TagsFieldProperties)properties));
typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime"); typeNameRegistry.MapObsolete(typeof(ReferencesFieldProperties), "DateTime");
typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References"); typeNameRegistry.MapObsolete(typeof(DateTimeFieldProperties), "References");
} }
private void Add<TFieldProperties>(FactoryFunction fieldFactory) private void RegisterField<T>()
{ {
Guard.NotNull(fieldFactory, nameof(fieldFactory)); typeNameRegistry.Map(typeof(T));
typeNameRegistry.Map(typeof(TFieldProperties));
var registered = new Registered(fieldFactory, typeof(TFieldProperties));
fieldsByPropertyType[registered.PropertiesType] = registered; fieldsByPropertyType[typeof(T)] = (id, name, partitioning, properties) => properties.CreateField(id, name, partitioning);
} }
public Field CreateField(long id, string name, Partitioning partitioning, FieldProperties properties) public Field CreateField(long id, string name, Partitioning partitioning, FieldProperties properties)
{ {
Guard.NotNull(properties, nameof(properties)); Guard.NotNull(properties, nameof(properties));
var registered = fieldsByPropertyType.GetOrDefault(properties.GetType()); var factory = fieldsByPropertyType.GetOrDefault(properties.GetType());
if (registered == null) if (factory == null)
{ {
throw new InvalidOperationException($"The field property '{properties.GetType()}' is not supported."); throw new InvalidOperationException($"The field property '{properties.GetType()}' is not supported.");
} }
return registered.CreateField(id, name, partitioning, properties); return factory(id, name, partitioning, properties);
} }
} }
} }

14
src/Squidex.Domain.Apps.Core.Model/Schemas/Field{T}.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// Field_Generic.cs // Field{T}.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -7,7 +7,9 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public abstract class Field<T> : Field where T : FieldProperties, new() public abstract class Field<T> : Field where T : FieldProperties, new()
@ -30,13 +32,19 @@ namespace Squidex.Domain.Apps.Core.Schemas
Guard.NotNull(properties, nameof(properties)); Guard.NotNull(properties, nameof(properties));
this.properties = properties; this.properties = properties;
this.properties.Freeze();
} }
public override void Update(FieldProperties newProperties) [Pure]
public override Field Update(FieldProperties newProperties)
{ {
var typedProperties = ValidateProperties(newProperties); var typedProperties = ValidateProperties(newProperties);
properties = typedProperties; return Clone<Field<T>>(clone =>
{
clone.properties = typedProperties;
clone.properties.Freeze();
});
} }
private T ValidateProperties(FieldProperties newProperties) private T ValidateProperties(FieldProperties newProperties)

21
src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs

@ -13,11 +13,30 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(GeolocationField))] [TypeName(nameof(GeolocationField))]
public sealed class GeolocationFieldProperties : FieldProperties public sealed class GeolocationFieldProperties : FieldProperties
{ {
public GeolocationFieldEditor Editor { get; set; } private GeolocationFieldEditor editor;
public GeolocationFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new GeolocationField(id, name, partitioning, this);
}
} }
} }

30
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs

@ -14,6 +14,8 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonSchemaModel public sealed class JsonSchemaModel
{ {
private static readonly Field[] Empty = new Field[0];
[JsonProperty] [JsonProperty]
public string Name { get; set; } public string Name { get; set; }
@ -54,46 +56,40 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public Schema ToSchema(FieldRegistry fieldRegistry) public Schema ToSchema(FieldRegistry fieldRegistry)
{ {
var schema = new Schema(Name); Field[] fields = Empty;
if (Fields != null) if (Fields != null)
{ {
foreach (var fieldModel in Fields) fields = new Field[Fields.Count];
for (var i = 0; i < fields.Length; i++)
{ {
var fieldModel = Fields[i];
var parititonKey = new Partitioning(fieldModel.Partitioning); var parititonKey = new Partitioning(fieldModel.Partitioning);
var field = fieldRegistry.CreateField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties); var field = fieldRegistry.CreateField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties);
if (fieldModel.IsDisabled) if (fieldModel.IsDisabled)
{ {
field.Disable(); field = field.Disable();
} }
if (fieldModel.IsLocked) if (fieldModel.IsLocked)
{ {
field.Lock(); field = field.Lock();
} }
if (fieldModel.IsHidden) if (fieldModel.IsHidden)
{ {
field.Hide(); field = field.Hide();
} }
schema.AddField(field); fields[i] = field;
} }
} }
if (IsPublished) return new Schema(Name, fields, Properties, IsPublished);
{
schema.Publish();
}
if (Properties != null)
{
schema.Update(Properties);
}
return schema;
} }
} }
} }

3
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
serializer.Serialize(writer, new JsonSchemaModel(value)); serializer.Serialize(writer, new JsonSchemaModel(value));
} }
protected override Schema ReadValue(JsonReader reader, JsonSerializer serializer) protected override Schema ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
return serializer.Deserialize<JsonSchemaModel>(reader).ToSchema(fieldRegistry); return serializer.Deserialize<JsonSchemaModel>(reader).ToSchema(fieldRegistry);
} }

5
src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs

@ -17,5 +17,10 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new JsonField(id, name, partitioning, this);
}
} }
} }

35
src/Squidex.Domain.Apps.Core.Model/Schemas/NamedElementPropertiesBase.cs

@ -6,12 +6,41 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public abstract class NamedElementPropertiesBase public abstract class NamedElementPropertiesBase : Freezable
{ {
public string Label { get; set; } private string label;
private string hints;
public string Label
{
get
{
return label;
}
set
{
ThrowIfFrozen();
label = value;
}
}
public string Hints
{
get
{
return hints;
}
set
{
ThrowIfFrozen();
public string Hints { get; set; } hints = value;
}
}
} }
} }

82
src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
@ -13,19 +14,90 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(NumberField))] [TypeName(nameof(NumberField))]
public sealed class NumberFieldProperties : FieldProperties public sealed class NumberFieldProperties : FieldProperties
{ {
public double? MaxValue { get; set; } private double? maxValue;
private double? minValue;
private double? defaultValue;
private ImmutableList<double> allowedValues;
private NumberFieldEditor editor;
public double? MinValue { get; set; } public double? MaxValue
{
get
{
return maxValue;
}
set
{
ThrowIfFrozen();
maxValue = value;
}
}
public double? MinValue
{
get
{
return minValue;
}
set
{
ThrowIfFrozen();
public double? DefaultValue { get; set; } minValue = value;
}
}
public double[] AllowedValues { get; set; } public double? DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
public NumberFieldEditor Editor { get; set; } defaultValue = value;
}
}
public ImmutableList<double> AllowedValues
{
get
{
return allowedValues;
}
set
{
ThrowIfFrozen();
allowedValues = value;
}
}
public NumberFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new NumberField(id, name, partitioning, this);
}
} }
} }

51
src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -14,15 +14,60 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(ReferencesField))] [TypeName(nameof(ReferencesField))]
public sealed class ReferencesFieldProperties : FieldProperties public sealed class ReferencesFieldProperties : FieldProperties
{ {
public int? MinItems { get; set; } private int? minItems;
private int? maxItems;
private Guid schemaId;
public int? MaxItems { get; set; } public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public Guid SchemaId { get; set; } public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public Guid SchemaId
{
get
{
return schemaId;
}
set
{
ThrowIfFrozen();
schemaId = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new ReferencesField(id, name, partitioning, this);
}
} }
} }

208
src/Squidex.Domain.Apps.Core.Model/Schemas/Schema.cs

@ -8,18 +8,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public sealed class Schema public sealed class Schema : Cloneable<Schema>
{ {
private readonly string name; private readonly string name;
private readonly List<Field> fieldsOrdered = new List<Field>(); private ImmutableArray<Field> fieldsOrdered = ImmutableArray<Field>.Empty;
private readonly Dictionary<long, Field> fieldsById = new Dictionary<long, Field>(); private ImmutableDictionary<long, Field> fieldsById;
private readonly Dictionary<string, Field> fieldsByName = new Dictionary<string, Field>(); private ImmutableDictionary<string, Field> fieldsByName;
private SchemaProperties properties = new SchemaProperties(); private SchemaProperties properties;
private bool isPublished; private bool isPublished;
public string Name public string Name
@ -39,12 +41,42 @@ namespace Squidex.Domain.Apps.Core.Schemas
public IReadOnlyDictionary<long, Field> FieldsById public IReadOnlyDictionary<long, Field> FieldsById
{ {
get { return fieldsById; } get
{
if (fieldsById == null)
{
if (fieldsOrdered.Length == 0)
{
fieldsById = ImmutableDictionary<long, Field>.Empty;
}
else
{
fieldsById = fieldsOrdered.ToImmutableDictionary(x => x.Id);
}
}
return fieldsById;
}
} }
public IReadOnlyDictionary<string, Field> FieldsByName public IReadOnlyDictionary<string, Field> FieldsByName
{ {
get { return fieldsByName; } get
{
if (fieldsByName == null)
{
if (fieldsOrdered.Length == 0)
{
fieldsByName = ImmutableDictionary<string, Field>.Empty;
}
else
{
fieldsByName = fieldsOrdered.ToImmutableDictionary(x => x.Name);
}
}
return fieldsByName;
}
} }
public SchemaProperties Properties public SchemaProperties Properties
@ -52,69 +84,177 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return properties; } get { return properties; }
} }
public void Publish() public Schema(string name, SchemaProperties properties = null)
{ {
isPublished = true; Guard.NotNullOrEmpty(name, nameof(name));
}
public void Unpublish() this.name = name;
{
isPublished = false; this.properties = properties ?? new SchemaProperties();
this.properties.Freeze();
} }
public Schema(string name) public Schema(string name, Field[] fields, SchemaProperties properties, bool isPublished)
: this(name, properties)
{ {
Guard.NotNullOrEmpty(name, nameof(name)); Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(fields, nameof(fields));
this.name = name; this.isPublished = isPublished;
fieldsOrdered = ImmutableArray.Create(fields);
}
protected override void OnCloned()
{
fieldsById = null;
fieldsByName = null;
} }
public void Update(SchemaProperties newProperties) [Pure]
public Schema Update(SchemaProperties newProperties)
{ {
Guard.NotNull(newProperties, nameof(newProperties)); Guard.NotNull(newProperties, nameof(newProperties));
properties = newProperties; return Clone(clone =>
{
clone.properties = newProperties;
clone.properties.Freeze();
});
}
[Pure]
public Schema UpdateField(long fieldId, FieldProperties newProperties)
{
return UpdateField(fieldId, field =>
{
return field.Update(newProperties);
});
} }
public void DeleteField(long fieldId) [Pure]
public Schema LockField(long fieldId)
{ {
if (!fieldsById.TryGetValue(fieldId, out var field)) return UpdateField(fieldId, field =>
{ {
return; return field.Lock();
});
}
[Pure]
public Schema DisableField(long fieldId)
{
return UpdateField(fieldId, field =>
{
return field.Disable();
});
}
[Pure]
public Schema EnableField(long fieldId)
{
return UpdateField(fieldId, field =>
{
return field.Enable();
});
}
[Pure]
public Schema HideField(long fieldId)
{
return UpdateField(fieldId, field =>
{
return field.Hide();
});
}
[Pure]
public Schema ShowField(long fieldId)
{
return UpdateField(fieldId, field =>
{
return field.Show();
});
}
[Pure]
public Schema Publish()
{
return Clone(clone =>
{
clone.isPublished = true;
});
}
[Pure]
public Schema Unpublish()
{
return Clone(clone =>
{
clone.isPublished = false;
});
}
[Pure]
public Schema DeleteField(long fieldId)
{
if (!FieldsById.TryGetValue(fieldId, out var field))
{
return this;
} }
fieldsById.Remove(fieldId); return Clone(clone =>
fieldsByName.Remove(field.Name); {
fieldsOrdered.Remove(field); clone.fieldsOrdered = fieldsOrdered.Remove(field);
});
} }
public void ReorderFields(List<long> ids) [Pure]
public Schema ReorderFields(List<long> ids)
{ {
Guard.NotNull(ids, nameof(ids)); Guard.NotNull(ids, nameof(ids));
if (ids.Count != fieldsOrdered.Count || ids.Any(x => !fieldsById.ContainsKey(x))) if (ids.Count != fieldsOrdered.Length || ids.Any(x => !FieldsById.ContainsKey(x)))
{ {
throw new ArgumentException("Ids must cover all fields.", nameof(ids)); throw new ArgumentException("Ids must cover all fields.", nameof(ids));
} }
var fields = fieldsOrdered.ToList(); return Clone(clone =>
{
fieldsOrdered.Clear(); clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableArray();
fieldsOrdered.AddRange(fields.OrderBy(f => ids.IndexOf(f.Id))); });
} }
public void AddField(Field field) [Pure]
public Schema AddField(Field field)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
if (fieldsByName.ContainsKey(field.Name) || fieldsById.ContainsKey(field.Id)) if (FieldsByName.ContainsKey(field.Name) || FieldsById.ContainsKey(field.Id))
{ {
throw new ArgumentException($"A field with name '{field.Name}' and id {field.Id} already exists.", nameof(field)); throw new ArgumentException($"A field with name '{field.Name}' and id {field.Id} already exists.", nameof(field));
} }
fieldsById.Add(field.Id, field); return Clone(clone =>
fieldsByName.Add(field.Name, field); {
fieldsOrdered.Add(field); clone.fieldsOrdered = clone.fieldsOrdered.Add(field);
});
}
[Pure]
public Schema UpdateField(long fieldId, Func<Field, Field> updater)
{
Guard.NotNull(updater, nameof(updater));
if (!FieldsById.TryGetValue(fieldId, out var field))
{
return this;
}
return Clone(clone =>
{
clone.fieldsOrdered = clone.fieldsOrdered.Replace(field, updater(field));
});
} }
} }
} }

112
src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs

@ -6,6 +6,7 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
@ -13,23 +14,120 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(StringField))] [TypeName(nameof(StringField))]
public sealed class StringFieldProperties : FieldProperties public sealed class StringFieldProperties : FieldProperties
{ {
public int? MinLength { get; set; } private int? minLength;
private int? maxLength;
private string pattern;
private string patternMessage;
private string defaultValue;
private ImmutableList<string> allowedValues;
private StringFieldEditor editor;
public int? MaxLength { get; set; } public int? MinLength
{
get
{
return minLength;
}
set
{
ThrowIfFrozen();
public string DefaultValue { get; set; } minLength = value;
}
}
public string Pattern { get; set; } public int? MaxLength
{
get
{
return maxLength;
}
set
{
ThrowIfFrozen();
public string PatternMessage { get; set; } maxLength = value;
}
}
public string[] AllowedValues { get; set; } public string DefaultValue
{
get
{
return defaultValue;
}
set
{
ThrowIfFrozen();
public StringFieldEditor Editor { get; set; } defaultValue = value;
}
}
public string Pattern
{
get
{
return pattern;
}
set
{
ThrowIfFrozen();
pattern = value;
}
}
public string PatternMessage
{
get
{
return patternMessage;
}
set
{
ThrowIfFrozen();
patternMessage = value;
}
}
public ImmutableList<string> AllowedValues
{
get
{
return allowedValues;
}
set
{
ThrowIfFrozen();
allowedValues = value;
}
}
public StringFieldEditor Editor
{
get
{
return editor;
}
set
{
ThrowIfFrozen();
editor = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new StringField(id, name, partitioning, this);
}
} }
} }

36
src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs

@ -13,13 +13,45 @@ namespace Squidex.Domain.Apps.Core.Schemas
[TypeName(nameof(TagsField))] [TypeName(nameof(TagsField))]
public sealed class TagsFieldProperties : FieldProperties public sealed class TagsFieldProperties : FieldProperties
{ {
public int? MinItems { get; set; } private int? minItems;
private int? maxItems;
public int? MaxItems { get; set; } public int? MinItems
{
get
{
return minItems;
}
set
{
ThrowIfFrozen();
minItems = value;
}
}
public int? MaxItems
{
get
{
return maxItems;
}
set
{
ThrowIfFrozen();
maxItems = value;
}
}
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override Field CreateField(long id, string name, Partitioning partitioning)
{
return new TagsField(id, name, partitioning, this);
}
} }
} }

4
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public static IEnumerable<Guid> Visit(AssetsField field, JToken value) public static IEnumerable<Guid> Visit(AssetsField field, JToken value)
{ {
IEnumerable<Guid> result = null; IEnumerable<Guid> result;
try try
{ {
result = value?.ToObject<List<Guid>>(); result = value?.ToObject<List<Guid>>();
@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
private static IEnumerable<Guid> Visit(ReferencesField field, JToken value) private static IEnumerable<Guid> Visit(ReferencesField field, JToken value)
{ {
IEnumerable<Guid> result = null; IEnumerable<Guid> result;
try try
{ {
result = value?.ToObject<List<Guid>>() ?? Enumerable.Empty<Guid>(); result = value?.ToObject<List<Guid>>() ?? Enumerable.Empty<Guid>();

2
src/Squidex.Domain.Apps.Core.Operations/HandleRules/Actions/WebhookActionHandler.cs

@ -20,7 +20,7 @@ using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
namespace Squidex.Domain.Apps.Core.HandleRules.ActionHandlers namespace Squidex.Domain.Apps.Core.HandleRules.Actions
{ {
public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction> public sealed class WebhookActionHandler : RuleActionHandler<WebhookAction>
{ {

6
src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -116,11 +116,11 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
catch (ParserException ex) catch (ParserException ex)
{ {
throw new ValidationException($"Failed to execute script with javascript syntaxs error.", new ValidationError(ex.Message)); throw new ValidationException("Failed to execute script with javascript syntaxs error.", new ValidationError(ex.Message));
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
throw new ValidationException($"Failed to execute script with javascript error.", new ValidationError(ex.Message)); throw new ValidationException("Failed to execute script with javascript error.", new ValidationError(ex.Message));
} }
} }
@ -171,7 +171,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null; var errors = !string.IsNullOrWhiteSpace(message) ? new[] { new ValidationError(message) } : null;
throw new ValidationException($"Script rejected the operation.", errors); throw new ValidationException("Script rejected the operation.", errors);
})); }));
} }
} }

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

@ -16,9 +16,9 @@
<PackageReference Include="Jint" Version="2.11.23" /> <PackageReference Include="Jint" Version="2.11.23" />
<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.8.3" /> <PackageReference Include="NJsonSchema" Version="9.10.9" />
<PackageReference Include="NodaTime" Version="2.2.1" /> <PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" /> <PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" /> <PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.4.0" />

46
src/Squidex.Domain.Apps.Events/Apps/Utils/AppEventDispatcher.cs

@ -13,53 +13,47 @@ namespace Squidex.Domain.Apps.Events.Apps.Utils
{ {
public static class AppEventDispatcher public static class AppEventDispatcher
{ {
public static void Apply(this AppContributors contributors, AppContributorRemoved @event) public static AppContributors Apply(this AppContributors contributors, AppContributorRemoved @event)
{ {
contributors.Remove(@event.ContributorId); return contributors.Remove(@event.ContributorId);
} }
public static void Apply(this AppContributors contributors, AppContributorAssigned @event) public static AppContributors Apply(this AppContributors contributors, AppContributorAssigned @event)
{ {
contributors.Assign(@event.ContributorId, @event.Permission); return contributors.Assign(@event.ContributorId, @event.Permission);
} }
public static void Apply(this LanguagesConfig languagesConfig, AppLanguageAdded @event) public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageAdded @event)
{ {
languagesConfig.Set(new LanguageConfig(@event.Language)); return languagesConfig.Set(new LanguageConfig(@event.Language));
} }
public static void Apply(this LanguagesConfig languagesConfig, AppLanguageRemoved @event) public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageRemoved @event)
{ {
languagesConfig.Remove(@event.Language); return languagesConfig.Remove(@event.Language);
} }
public static void Apply(this AppClients clients, AppClientAttached @event) public static AppClients Apply(this AppClients clients, AppClientAttached @event)
{ {
clients.Add(@event.Id, @event.Secret); return clients.Add(@event.Id, @event.Secret);
} }
public static void Apply(this AppClients clients, AppClientRevoked @event) public static AppClients Apply(this AppClients clients, AppClientRevoked @event)
{ {
clients.Revoke(@event.Id); return clients.Revoke(@event.Id);
} }
public static void Apply(this AppClients clients, AppClientRenamed @event) public static AppClients Apply(this AppClients clients, AppClientRenamed @event)
{ {
if (clients.TryGetValue(@event.Id, out var client)) return clients.Rename(@event.Id, @event.Name);
{
client.Rename(@event.Name);
}
} }
public static void Apply(this AppClients clients, AppClientUpdated @event) public static AppClients Apply(this AppClients clients, AppClientUpdated @event)
{ {
if (clients.TryGetValue(@event.Id, out var client)) return clients.Update(@event.Id, @event.Permission);
{
client.Update(@event.Permission);
}
} }
public static void Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event) public static LanguagesConfig Apply(this LanguagesConfig languagesConfig, AppLanguageUpdated @event)
{ {
var fallback = @event.Fallback; var fallback = @event.Fallback;
@ -70,12 +64,14 @@ namespace Squidex.Domain.Apps.Events.Apps.Utils
fallback = fallback.Intersect(existingLangauges).ToList(); fallback = fallback.Intersect(existingLangauges).ToList();
} }
languagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, fallback)); languagesConfig = languagesConfig.Set(new LanguageConfig(@event.Language, @event.IsOptional, fallback));
if (@event.IsMaster) if (@event.IsMaster)
{ {
languagesConfig.MakeMaster(@event.Language); languagesConfig = languagesConfig.MakeMaster(@event.Language);
} }
return languagesConfig;
} }
} }
} }

16
src/Squidex.Domain.Apps.Events/Rules/Utils/RuleEventDispatcher.cs

@ -17,27 +17,29 @@ namespace Squidex.Domain.Apps.Events.Rules.Utils
return new Rule(@event.Trigger, @event.Action); return new Rule(@event.Trigger, @event.Action);
} }
public static void Apply(this Rule rule, RuleUpdated @event) public static Rule Apply(this Rule rule, RuleUpdated @event)
{ {
if (@event.Trigger != null) if (@event.Trigger != null)
{ {
rule.Update(@event.Trigger); return rule.Update(@event.Trigger);
} }
if (@event.Action != null) if (@event.Action != null)
{ {
rule.Update(@event.Action); return rule.Update(@event.Action);
} }
return rule;
} }
public static void Apply(this Rule rule, RuleEnabled @event) public static Rule Apply(this Rule rule, RuleEnabled @event)
{ {
rule.Enable(); return rule.Enable();
} }
public static void Apply(this Rule rule, RuleDisabled @event) public static Rule Apply(this Rule rule, RuleDisabled @event)
{ {
rule.Disable(); return rule.Disable();
} }
} }
} }

83
src/Squidex.Domain.Apps.Events/Schemas/Utils/SchemaEventDispatcher.cs

@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
if (@event.Properties != null) if (@event.Properties != null)
{ {
schema.Update(@event.Properties); schema = schema.Update(@event.Properties);
} }
if (@event.Fields != null) if (@event.Fields != null)
@ -38,20 +38,20 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
if (eventField.IsHidden) if (eventField.IsHidden)
{ {
field.Hide(); field = field.Hide();
} }
if (eventField.IsDisabled) if (eventField.IsDisabled)
{ {
field.Disable(); field = field.Disable();
} }
if (eventField.IsLocked) if (eventField.IsLocked)
{ {
field.Lock(); field = field.Lock();
} }
schema.AddField(field); schema = schema.AddField(field);
fieldId++; fieldId++;
} }
@ -60,91 +60,74 @@ namespace Squidex.Domain.Apps.Events.Schemas.Utils
return schema; return schema;
} }
public static void Apply(this Schema schema, FieldAdded @event, FieldRegistry registry) public static Schema Apply(this Schema schema, FieldAdded @event, FieldRegistry registry)
{ {
var partitioning = var partitioning =
string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ? string.Equals(@event.Partitioning, Partitioning.Language.Key, StringComparison.OrdinalIgnoreCase) ?
Partitioning.Language : Partitioning.Language :
Partitioning.Invariant; Partitioning.Invariant;
var fieldId = @event.FieldId.Id; var field = registry.CreateField(@event.FieldId.Id, @event.Name, partitioning, @event.Properties);
var field = registry.CreateField(fieldId, @event.Name, partitioning, @event.Properties);
schema.DeleteField(fieldId); schema = schema.DeleteField(@event.FieldId.Id);
schema.AddField(field); schema = schema.AddField(field);
return schema;
} }
public static void Apply(this Schema schema, FieldUpdated @event) public static Schema Apply(this Schema schema, FieldUpdated @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.UpdateField(@event.FieldId.Id, @event.Properties);
{
field.Update(@event.Properties);
}
} }
public static void Apply(this Schema schema, FieldLocked @event) public static Schema Apply(this Schema schema, FieldLocked @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.LockField(@event.FieldId.Id);
{
field.Lock();
}
} }
public static void Apply(this Schema schema, FieldHidden @event) public static Schema Apply(this Schema schema, FieldHidden @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.HideField(@event.FieldId.Id);
{
field.Hide();
}
} }
public static void Apply(this Schema schema, FieldShown @event) public static Schema Apply(this Schema schema, FieldShown @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.ShowField(@event.FieldId.Id);
{
field.Show();
}
} }
public static void Apply(this Schema schema, FieldDisabled @event) public static Schema Apply(this Schema schema, FieldDisabled @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.DisableField(@event.FieldId.Id);
{
field.Disable();
}
} }
public static void Apply(this Schema schema, FieldEnabled @event) public static Schema Apply(this Schema schema, FieldEnabled @event)
{ {
if (schema.FieldsById.TryGetValue(@event.FieldId.Id, out var field)) return schema.EnableField(@event.FieldId.Id);
{
field.Enable();
}
} }
public static void Apply(this Schema schema, SchemaUpdated @event) public static Schema Apply(this Schema schema, SchemaUpdated @event)
{ {
schema.Update(@event.Properties); return schema.Update(@event.Properties);
} }
public static void Apply(this Schema schema, SchemaFieldsReordered @event) public static Schema Apply(this Schema schema, SchemaFieldsReordered @event)
{ {
schema.ReorderFields(@event.FieldIds); return schema.ReorderFields(@event.FieldIds);
} }
public static void Apply(this Schema schema, FieldDeleted @event) public static Schema Apply(this Schema schema, FieldDeleted @event)
{ {
schema.DeleteField(@event.FieldId.Id); return schema.DeleteField(@event.FieldId.Id);
} }
public static void Apply(this Schema schema, SchemaPublished @event) public static Schema Apply(this Schema schema, SchemaPublished @event)
{ {
schema.Publish(); return schema.Publish();
} }
public static void Apply(this Schema schema, SchemaUnpublished @event) public static Schema Apply(this Schema schema, SchemaUnpublished @event)
{ {
schema.Unpublish(); return schema.Unpublish();
} }
} }
} }

4
src/Squidex.Domain.Apps.Events/Squidex.Domain.Apps.Events.csproj

@ -11,8 +11,8 @@
<ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> <ProjectReference Include="..\Squidex.Infrastructure\Squidex.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NodaTime" Version="2.2.1" /> <PackageReference Include="NodaTime" Version="2.2.3" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" /> <PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup> </ItemGroup>

59
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppEntity.cs

@ -1,59 +0,0 @@
// ==========================================================================
// MongoAppEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public sealed class MongoAppEntity : MongoEntity, IAppEntity
{
[BsonRequired]
[BsonElement]
public string Name { get; set; }
[BsonRequired]
[BsonElement]
public long Version { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string PlanId { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string PlanOwner { get; set; }
[BsonIgnoreIfDefault]
[BsonElement]
public string[] ContributorIds { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public AppClients Clients { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public AppContributors Contributors { get; set; }
[BsonRequired]
[BsonElement]
[BsonJson]
public LanguagesConfig LanguagesConfig { get; set; }
public PartitionResolver PartitionResolver
{
get { return LanguagesConfig.ToResolver(); }
}
}
}

70
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository.cs

@ -1,70 +0,0 @@
// ==========================================================================
// 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.Read.Apps;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public partial class MongoAppRepository : MongoRepositoryBase<MongoAppEntity>, IAppRepository, IAppEventConsumer
{
public MongoAppRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_Apps";
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoAppEntity> collection)
{
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name));
await collection.Indexes.CreateOneAsync(Index.Ascending(x => x.ContributorIds));
}
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings { WriteConcern = WriteConcern.WMajority };
}
public async Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId)
{
var appEntities =
await Collection.Find(s => s.ContributorIds.Contains(subjectId))
.ToListAsync();
return appEntities;
}
public async Task<IAppEntity> FindAppAsync(Guid id)
{
var appEntity =
await Collection.Find(s => s.Id == id)
.FirstOrDefaultAsync();
return appEntity;
}
public async Task<IAppEntity> FindAppAsync(string name)
{
var appEntity =
await Collection.Find(s => s.Name == name)
.FirstOrDefaultAsync();
return appEntity;
}
}
}

143
src/Squidex.Domain.Apps.Read.MongoDb/Apps/MongoAppRepository_EventHandling.cs

@ -1,143 +0,0 @@
// ==========================================================================
// MongoAppRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Domain.Apps.Events.Apps.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Read.MongoDb.Apps
{
public partial class MongoAppRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^app-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(AppCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
a.Clients = new AppClients();
a.Contributors = new AppContributors();
a.LanguagesConfig = LanguagesConfig.Build(Language.EN);
});
}
protected Task On(AppPlanChanged @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
SimpleMapper.Map(@event, a);
});
}
protected Task On(AppClientAttached @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientRevoked @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientRenamed @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppClientUpdated @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Clients.Apply(@event);
});
}
protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Contributors.Apply(@event);
});
}
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.Contributors.Apply(@event);
});
}
protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
protected Task On(AppLanguageUpdated @event, EnvelopeHeaders headers)
{
return UpdateAppAsync(@event, headers, a =>
{
a.LanguagesConfig.Apply(@event);
});
}
private async Task UpdateAppAsync(AppEvent @event, EnvelopeHeaders headers, Action<MongoAppEntity> updater)
{
await Collection.UpdateAsync(@event, headers, a =>
{
updater(a);
a.ContributorIds = a.Contributors.Keys.ToArray();
});
}
}
}

7
src/Squidex.Domain.Apps.Read.MongoDb/Assets/MongoAssetEntity.cs

@ -14,7 +14,12 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Assets namespace Squidex.Domain.Apps.Read.MongoDb.Assets
{ {
public sealed class MongoAssetEntity : MongoEntity, IAssetEntity public sealed class MongoAssetEntity :
MongoEntity,
IAssetEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]

6
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentEntity.cs

@ -19,7 +19,11 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Contents namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{ {
public sealed class MongoContentEntity : IContentEntity, IMongoEntity public sealed class MongoContentEntity :
IContentEntity,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy,
IUpdateableEntityWithLastModifiedBy
{ {
private NamedContentData data; private NamedContentData data;

15
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository.cs

@ -19,7 +19,6 @@ using Squidex.Domain.Apps.Read.Contents;
using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Read.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
@ -29,7 +28,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
{ {
private const string Prefix = "Projections_Content_"; private const string Prefix = "Projections_Content_";
private readonly IMongoDatabase database; private readonly IMongoDatabase database;
private readonly ISchemaProvider schemas; private readonly IAppProvider appProvider;
protected static FilterDefinitionBuilder<MongoContentEntity> Filter protected static FilterDefinitionBuilder<MongoContentEntity> Filter
{ {
@ -63,13 +62,13 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
} }
} }
public MongoContentRepository(IMongoDatabase database, ISchemaProvider schemas) public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
{ {
Guard.NotNull(database, nameof(database)); Guard.NotNull(database, nameof(database));
Guard.NotNull(schemas, nameof(schemas)); Guard.NotNull(appProvider, nameof(appProvider));
this.database = database; this.database = database;
this.schemas = schemas; this.appProvider = appProvider;
} }
public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery) public async Task<IReadOnlyList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, ODataUriParser odataQuery)
@ -177,11 +176,11 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
return contentEntity; return contentEntity;
} }
private async Task ForSchemaAsync(Guid appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action) private async Task ForSchemaAsync(NamedId<Guid> appId, Guid schemaId, Func<IMongoCollection<MongoContentEntity>, ISchemaEntity, Task> action)
{ {
var collection = GetCollection(appId); var collection = GetCollection(appId.Id);
var schema = await schemas.FindSchemaByIdAsync(schemaId, true); var schema = await appProvider.GetSchemaAsync(appId.Name, schemaId, true);
if (schema == null) if (schema == null)
{ {

4
src/Squidex.Domain.Apps.Read.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
protected Task On(ContentCreated @event, EnvelopeHeaders headers) protected Task On(ContentCreated @event, EnvelopeHeaders headers)
{ {
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) => return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{ {
return collection.CreateAsync(@event, headers, content => return collection.CreateAsync(@event, headers, content =>
{ {
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Contents
protected Task On(ContentUpdated @event, EnvelopeHeaders headers) protected Task On(ContentUpdated @event, EnvelopeHeaders headers)
{ {
return ForSchemaAsync(@event.AppId.Id, @event.SchemaId.Id, (collection, schema) => return ForSchemaAsync(@event.AppId, @event.SchemaId.Id, (collection, schema) =>
{ {
var idData = @event.Data?.ToIdModel(schema.SchemaDef, true); var idData = @event.Data?.ToIdModel(schema.SchemaDef, true);

7
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventEntity.cs

@ -14,7 +14,10 @@ using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.History namespace Squidex.Domain.Apps.Read.MongoDb.History
{ {
public sealed class MongoHistoryEventEntity : MongoEntity, IAppRefEntity, IEntityWithCreatedBy public sealed class MongoHistoryEventEntity : MongoEntity,
IEntityWithAppRef,
IUpdateableEntityWithVersion,
IUpdateableEntityWithCreatedBy
{ {
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
@ -40,7 +43,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
[BsonElement] [BsonElement]
public Dictionary<string, string> Parameters { get; set; } public Dictionary<string, string> Parameters { get; set; }
RefToken IEntityWithCreatedBy.CreatedBy RefToken IUpdateableEntityWithCreatedBy.CreatedBy
{ {
get get
{ {

6
src/Squidex.Domain.Apps.Read.MongoDb/History/MongoHistoryEventRepository.cs

@ -83,10 +83,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
if (message != null) if (message != null)
{ {
await Collection.CreateAsync((SquidexEvent)@event.Payload, @event.Headers, entity => var appEvent = (AppEvent)@event.Payload;
await Collection.CreateAsync(appEvent, @event.Headers, entity =>
{ {
entity.Id = Guid.NewGuid(); entity.Id = Guid.NewGuid();
entity.AppId = appEvent.AppId.Id;
entity.Version = @event.Headers.EventStreamNumber(); entity.Version = @event.Headers.EventStreamNumber();
entity.Channel = message.Channel; entity.Channel = message.Channel;

21
src/Squidex.Domain.Apps.Read.MongoDb/History/ParsedHistoryEvent.cs

@ -12,6 +12,8 @@ using NodaTime;
using Squidex.Domain.Apps.Read.History; using Squidex.Domain.Apps.Read.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
namespace Squidex.Domain.Apps.Read.MongoDb.History namespace Squidex.Domain.Apps.Read.MongoDb.History
{ {
internal sealed class ParsedHistoryEvent : IHistoryEventEntity internal sealed class ParsedHistoryEvent : IHistoryEventEntity
@ -22,26 +24,29 @@ namespace Squidex.Domain.Apps.Read.MongoDb.History
public Guid Id public Guid Id
{ {
get { return inner.Id; } get { return inner.Id; }
set { }
} }
public Guid EventId public Instant Created
{ {
get { return inner.Id; } get { return inner.Created; }
set { }
} }
public RefToken Actor public Instant LastModified
{ {
get { return inner.Actor; } get { return inner.LastModified; }
set { }
} }
public Instant Created public RefToken Actor
{ {
get { return inner.Created; } get { return inner.Actor; }
} }
public Instant LastModified public Guid EventId
{ {
get { return inner.LastModified; } get { return inner.Id; }
} }
public long Version public long Version

25
src/Squidex.Domain.Apps.Read.MongoDb/MongoCollectionExtensions.cs

@ -18,25 +18,14 @@ namespace Squidex.Domain.Apps.Read.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, IMongoEntity, 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<T>(@event, headers); var entity = EntityMapper.Create(@event, headers, updater);
updater(entity);
return collection.InsertOneIfNotExistsAsync(entity); return collection.InsertOneIfNotExistsAsync(entity);
} }
public static async Task CreateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Func<T, Task> updater) where T : class, IMongoEntity, new() public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{
var entity = EntityMapper.Create<T>(@event, headers);
await updater(entity);
await collection.InsertOneIfNotExistsAsync(entity);
}
public static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IMongoEntity, new()
{ {
var entity = var entity =
await collection.Find(t => t.Id == headers.AggregateId()) await collection.Find(t => t.Id == headers.AggregateId())
@ -50,7 +39,7 @@ namespace Squidex.Domain.Apps.Read.MongoDb
await collection.UpdateAsync(@event, headers, entity, updater); await collection.UpdateAsync(@event, headers, entity, updater);
} }
public static async Task<bool> TryUpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IMongoEntity, new() public static async Task<bool> TryUpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater) where T : class, IEntity, new()
{ {
var entity = var entity =
await collection.Find(t => t.Id == headers.AggregateId()) await collection.Find(t => t.Id == headers.AggregateId())
@ -76,11 +65,9 @@ namespace Squidex.Domain.Apps.Read.MongoDb
return false; return false;
} }
private static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, T entity, Action<T> updater) where T : class, IMongoEntity, new() private static async Task UpdateAsync<T>(this IMongoCollection<T> collection, SquidexEvent @event, EnvelopeHeaders headers, T entity, Action<T> updater) where T : class, IEntity, new()
{ {
EntityMapper.Update(@event, headers, entity); entity.Update(@event, headers, updater);
updater(entity);
await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity); await collection.ReplaceOneAsync(t => t.Id == entity.Id, entity);
} }

4
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventEntity.cs

@ -38,10 +38,6 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
[BsonElement] [BsonElement]
public Instant? NextAttempt { get; set; } public Instant? NextAttempt { get; set; }
[BsonRequired]
[BsonElement]
public bool IsSending { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement] [BsonElement]
public RuleResult Result { get; set; } public RuleResult Result { get; set; }

10
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleEventRepository.cs

@ -36,14 +36,14 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection) protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection)
{ {
return Task.WhenAll( return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt).Descending(x => x.IsSending)), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.NextAttempt)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Created)), collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Descending(x => x.Created)),
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))
{ {
return Collection.Find(x => x.NextAttempt < now && !x.IsSending).ForEachAsync(callback, cancellationToken); return Collection.Find(x => x.NextAttempt < now).ForEachAsync(callback, cancellationToken);
} }
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)
@ -81,18 +81,12 @@ namespace Squidex.Domain.Apps.Read.MongoDb.Rules
return Collection.InsertOneIfNotExistsAsync(entity); return Collection.InsertOneIfNotExistsAsync(entity);
} }
public Task MarkSendingAsync(Guid jobId)
{
return Collection.UpdateOneAsync(x => x.Id == jobId, Update.Set(x => x.IsSending, true));
}
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.IsSending, false)
.Set(x => x.NextAttempt, nextAttempt) .Set(x => x.NextAttempt, nextAttempt)
.Inc(x => x.NumCalls, 1)); .Inc(x => x.NumCalls, 1));
} }

90
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository.cs

@ -1,90 +0,0 @@
// ==========================================================================
// MongoRuleRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
public partial class MongoRuleRepository : MongoRepositoryBase<MongoRuleEntity>, IRuleRepository, IEventConsumer
{
private static readonly List<IRuleEntity> EmptyRules = new List<IRuleEntity>();
private readonly SemaphoreSlim lockObject = new SemaphoreSlim(1);
private Dictionary<Guid, List<IRuleEntity>> inMemoryRules;
public MongoRuleRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_Rules";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoRuleEntity> collection)
{
return Task.WhenAll(collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId)));
}
public async Task<IReadOnlyList<IRuleEntity>> QueryByAppAsync(Guid appId)
{
var entities =
await Collection.Find(x => x.AppId == appId)
.ToListAsync();
return entities.OfType<IRuleEntity>().ToList();
}
public async Task<IReadOnlyList<IRuleEntity>> QueryCachedByAppAsync(Guid appId)
{
await EnsureRulesLoadedAsync();
return inMemoryRules.GetOrDefault(appId)?.ToList() ?? EmptyRules;
}
private async Task EnsureRulesLoadedAsync()
{
if (inMemoryRules == null)
{
try
{
await lockObject.WaitAsync();
if (inMemoryRules == null)
{
inMemoryRules = new Dictionary<Guid, List<IRuleEntity>>();
var webhooks =
await Collection.Find(new BsonDocument())
.ToListAsync();
foreach (var webhook in webhooks)
{
inMemoryRules.GetOrAddNew(webhook.AppId).Add(webhook);
}
}
}
finally
{
lockObject.Release();
}
}
}
}
}

97
src/Squidex.Domain.Apps.Read.MongoDb/Rules/MongoRuleRepository_EventHandling.cs

@ -1,97 +0,0 @@
// ==========================================================================
// MongoRuleRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Domain.Apps.Events.Rules.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
namespace Squidex.Domain.Apps.Read.MongoDb.Rules
{
public partial class MongoRuleRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^rule-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected async Task On(RuleCreated @event, EnvelopeHeaders headers)
{
await EnsureRulesLoadedAsync();
await Collection.CreateAsync(@event, headers, w =>
{
w.Rule = RuleEventDispatcher.Create(@event);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
protected async Task On(RuleUpdated @event, EnvelopeHeaders headers)
{
await EnsureRulesLoadedAsync();
await Collection.UpdateAsync(@event, headers, w =>
{
w.Rule.Apply(@event);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
protected async Task On(RuleEnabled @event, EnvelopeHeaders headers)
{
await EnsureRulesLoadedAsync();
await Collection.UpdateAsync(@event, headers, w =>
{
w.Rule.Apply(@event);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
protected async Task On(RuleDisabled @event, EnvelopeHeaders headers)
{
await EnsureRulesLoadedAsync();
await Collection.UpdateAsync(@event, headers, w =>
{
w.Rule.Apply(@event);
inMemoryRules.GetOrAddNew(w.AppId).RemoveAll(x => x.Id == w.Id);
inMemoryRules.GetOrAddNew(w.AppId).Add(w);
});
}
protected async Task On(RuleDeleted @event, EnvelopeHeaders headers)
{
await EnsureRulesLoadedAsync();
inMemoryRules.GetOrAddNew(@event.AppId.Id).RemoveAll(x => x.Id == @event.RuleId);
await Collection.DeleteManyAsync(x => x.Id == @event.RuleId);
}
}
}

73
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository.cs

@ -1,73 +0,0 @@
// ==========================================================================
// 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.Core.Schemas;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
{
public partial class MongoSchemaRepository : MongoRepositoryBase<MongoSchemaEntity>, ISchemaRepository, ISchemaEventConsumer
{
private readonly FieldRegistry registry;
public MongoSchemaRepository(IMongoDatabase database, FieldRegistry registry)
: base(database)
{
Guard.NotNull(registry, nameof(registry));
this.registry = registry;
}
protected override string CollectionName()
{
return "Projections_Schemas";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoSchemaEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.Name)),
collection.Indexes.CreateOneAsync(Index.Ascending(x => x.AppId).Ascending(x => x.IsDeleted).Ascending(x => x.Name)));
}
public async Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId)
{
var schemaEntities =
await Collection.Find(s => s.AppId == appId && !s.IsDeleted)
.ToListAsync();
return schemaEntities.OfType<ISchemaEntity>().ToList();
}
public async Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name)
{
var schemaEntity =
await Collection.Find(s => s.AppId == appId && !s.IsDeleted && s.Name == name)
.FirstOrDefaultAsync();
return schemaEntity;
}
public async Task<ISchemaEntity> FindSchemaAsync(Guid schemaId)
{
var schemaEntity =
await Collection.Find(s => s.Id == schemaId)
.FirstOrDefaultAsync();
return schemaEntity;
}
}
}

188
src/Squidex.Domain.Apps.Read.MongoDb/Schemas/MongoSchemaRepository_EventHandling.cs

@ -1,188 +0,0 @@
// ==========================================================================
// MongoSchemaRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Domain.Apps.Events.Schemas.Old;
using Squidex.Domain.Apps.Events.Schemas.Utils;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
using Squidex.Infrastructure.Reflection;
#pragma warning disable CS0612 // Type or member is obsolete
namespace Squidex.Domain.Apps.Read.MongoDb.Schemas
{
public partial class MongoSchemaRepository
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^schema-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(SchemaCreated @event, EnvelopeHeaders headers)
{
return Collection.CreateAsync(@event, headers, s =>
{
s.SchemaDef = SchemaEventDispatcher.Create(@event, registry);
SimpleMapper.Map(@event, s);
});
}
protected Task On(FieldDeleted @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldLocked @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldHidden @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldShown @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldDisabled @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldEnabled @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldUpdated @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaFieldsReordered @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaUpdated @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaPublished @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(SchemaUnpublished @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event);
});
}
protected Task On(FieldAdded @event, EnvelopeHeaders headers)
{
return UpdateSchemaAsync(@event, headers, s =>
{
s.SchemaDef.Apply(@event, registry);
});
}
protected Task On(ScriptsConfigured @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
SimpleMapper.Map(@event, s);
});
}
protected Task On(SchemaDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
s.IsDeleted = true;
});
}
protected Task On(WebhookAdded @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
/* NOOP */
});
}
protected Task On(WebhookDeleted @event, EnvelopeHeaders headers)
{
return Collection.UpdateAsync(@event, headers, s =>
{
/* NOOP */
});
}
private Task UpdateSchemaAsync(SquidexEvent @event, EnvelopeHeaders headers, Action<MongoSchemaEntity> updater)
{
return Collection.UpdateAsync(@event, headers, s =>
{
updater(s);
s.IsPublished = s.SchemaDef.IsPublished;
});
}
}
}

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

@ -17,7 +17,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.OData.Core" Version="7.3.1" /> <PackageReference Include="Microsoft.OData.Core" Version="7.3.1" />
<PackageReference Include="MongoDB.Driver" Version="2.4.4" /> <PackageReference Include="MongoDB.Driver" Version="2.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.2.0" /> <PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup> </ItemGroup>

14
src/Squidex/Config/Identity/AuthenticationExtensions.cs → src/Squidex.Domain.Apps.Read/Apps/AppEntityExtensions.cs

@ -1,22 +1,20 @@
// ========================================================================== // ==========================================================================
// AuthenticationExtensions.cs // AppEntityExtensions.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Microsoft.AspNetCore.Builder; using Squidex.Domain.Apps.Core;
namespace Squidex.Config.Identity namespace Squidex.Domain.Apps.Read.Apps
{ {
public static class AuthenticationExtensions public static class AppEntityExtensions
{ {
public static IApplicationBuilder UseMyAuthentication(this IApplicationBuilder app) public static PartitionResolver PartitionResolver(this IAppEntity entity)
{ {
app.UseAuthentication(); return entity.LanguagesConfig.ToResolver();
return app;
} }
} }
} }

5
src/Squidex.Domain.Apps.Read/Apps/IAppEntity.cs

@ -6,13 +6,14 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
namespace Squidex.Domain.Apps.Read.Apps namespace Squidex.Domain.Apps.Read.Apps
{ {
public interface IAppEntity : IEntity, IEntityWithVersion public interface IAppEntity : IEntity, IEntityWithVersion
{ {
string Etag { get; }
string Name { get; } string Name { get; }
string PlanId { get; } string PlanId { get; }
@ -24,7 +25,5 @@ namespace Squidex.Domain.Apps.Read.Apps
AppContributors Contributors { get; } AppContributors Contributors { get; }
LanguagesConfig LanguagesConfig { get; } LanguagesConfig LanguagesConfig { get; }
PartitionResolver PartitionResolver { get; }
} }
} }

23
src/Squidex.Domain.Apps.Read/Apps/Repositories/IAppRepository.cs

@ -1,23 +0,0 @@
// ==========================================================================
// 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.Read.Apps.Repositories
{
public interface IAppRepository
{
Task<IReadOnlyList<IAppEntity>> QueryAllAsync(string subjectId);
Task<IAppEntity> FindAppAsync(Guid appId);
Task<IAppEntity> FindAppAsync(string name);
}
}

121
src/Squidex.Domain.Apps.Read/Apps/Services/Implementations/CachingAppProvider.cs

@ -1,121 +0,0 @@
// ==========================================================================
// CachingAppProvider.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps.Repositories;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Caching;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.Apps.Services.Implementations
{
public class CachingAppProvider : CachingProviderBase, IAppProvider, IEventConsumer
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30);
private readonly IAppRepository repository;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return string.Empty; }
}
public CachingAppProvider(IMemoryCache cache, IAppRepository repository)
: base(cache)
{
Guard.NotNull(cache, nameof(cache));
this.repository = repository;
}
public async Task<IAppEntity> FindAppByIdAsync(Guid appId)
{
var cacheKey = BuildIdCacheKey(appId);
if (!Cache.TryGetValue(cacheKey, out IAppEntity result))
{
result = await repository.FindAppAsync(appId);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildNameCacheKey(result.Name), result, CacheDuration);
}
}
return result;
}
public async Task<IAppEntity> FindAppByNameAsync(string name)
{
Guard.NotNullOrEmpty(name, nameof(name));
var cacheKey = BuildNameCacheKey(name);
if (!Cache.TryGetValue(cacheKey, out IAppEntity result))
{
result = await repository.FindAppAsync(name);
Cache.Set(cacheKey, result, CacheDuration);
if (result != null)
{
Cache.Set(BuildIdCacheKey(result.Id), result, CacheDuration);
}
}
return result;
}
public Task On(Envelope<IEvent> @event)
{
void Remove(NamedId<Guid> id)
{
var cacheKeyById = BuildIdCacheKey(id.Id);
var cacheKeyByName = BuildNameCacheKey(id.Name);
Cache.Remove(cacheKeyById);
Cache.Remove(cacheKeyByName);
Cache.Invalidate(cacheKeyById);
Cache.Invalidate(cacheKeyByName);
}
if (@event.Payload is AppEvent appEvent)
{
Remove(appEvent.AppId);
}
return TaskHelper.Done;
}
private static string BuildNameCacheKey(string name)
{
return $"App_Ids_{name}";
}
private static string BuildIdCacheKey(Guid schemaId)
{
return $"App_Names_{schemaId}";
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
}
}

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

@ -8,7 +8,7 @@
namespace Squidex.Domain.Apps.Read.Assets namespace Squidex.Domain.Apps.Read.Assets
{ {
public interface IAssetEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion public interface IAssetEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{ {
string MimeType { get; } string MimeType { get; }

11
src/Squidex.Domain.Apps.Read/Contents/ContentHistoryEventsCreator.cs

@ -29,14 +29,21 @@ namespace Squidex.Domain.Apps.Read.Contents
"deleted content item."); "deleted content item.");
AddEventMessage<ContentStatusChanged>( AddEventMessage<ContentStatusChanged>(
"change status of content item to {[Status]}."); "changed status of content item to {[Status]}.");
} }
protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event) protected override Task<HistoryEventToStore> CreateEventCoreAsync(Envelope<IEvent> @event)
{ {
var channel = $"contents.{@event.Headers.AggregateId()}"; var channel = $"contents.{@event.Headers.AggregateId()}";
return Task.FromResult(ForEvent(@event.Payload, channel)); var result = ForEvent(@event.Payload, channel);
if (@event.Payload is ContentStatusChanged contentStatusChanged)
{
result = result.AddParameter("Status", contentStatusChanged.Status);
}
return Task.FromResult(result);
} }
} }
} }

13
src/Squidex.Domain.Apps.Read/Contents/ContentQueryService.cs

@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Contents.Edm; using Squidex.Domain.Apps.Read.Contents.Edm;
using Squidex.Domain.Apps.Read.Contents.Repositories; using Squidex.Domain.Apps.Read.Contents.Repositories;
using Squidex.Domain.Apps.Read.Schemas; using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Domain.Apps.Read.Schemas.Services;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
@ -30,23 +29,23 @@ namespace Squidex.Domain.Apps.Read.Contents
public sealed class ContentQueryService : IContentQueryService public sealed class ContentQueryService : IContentQueryService
{ {
private readonly IContentRepository contentRepository; private readonly IContentRepository contentRepository;
private readonly ISchemaProvider schemas; private readonly IAppProvider appProvider;
private readonly IScriptEngine scriptEngine; private readonly IScriptEngine scriptEngine;
private readonly EdmModelBuilder modelBuilder; private readonly EdmModelBuilder modelBuilder;
public ContentQueryService( public ContentQueryService(
IContentRepository contentRepository, IContentRepository contentRepository,
ISchemaProvider schemas, IAppProvider appProvider,
IScriptEngine scriptEngine, IScriptEngine scriptEngine,
EdmModelBuilder modelBuilder) EdmModelBuilder modelBuilder)
{ {
Guard.NotNull(contentRepository, nameof(contentRepository)); Guard.NotNull(contentRepository, nameof(contentRepository));
Guard.NotNull(scriptEngine, nameof(scriptEngine)); Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(modelBuilder, nameof(modelBuilder)); Guard.NotNull(modelBuilder, nameof(modelBuilder));
Guard.NotNull(schemas, nameof(schemas)); Guard.NotNull(appProvider, nameof(appProvider));
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
this.schemas = schemas; this.appProvider = appProvider;
this.scriptEngine = scriptEngine; this.scriptEngine = scriptEngine;
this.modelBuilder = modelBuilder; this.modelBuilder = modelBuilder;
} }
@ -156,12 +155,12 @@ namespace Squidex.Domain.Apps.Read.Contents
if (Guid.TryParse(schemaIdOrName, out var id)) if (Guid.TryParse(schemaIdOrName, out var id))
{ {
schema = await schemas.FindSchemaByIdAsync(id); schema = await appProvider.GetSchemaAsync(app.Name, id);
} }
if (schema == null) if (schema == null)
{ {
schema = await schemas.FindSchemaByNameAsync(app.Id, schemaIdOrName); schema = await appProvider.GetSchemaAsync(app.Name, schemaIdOrName);
} }
if (schema == null) if (schema == null)

2
src/Squidex.Domain.Apps.Read/Contents/Edm/EdmModelBuilder.cs

@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Read.Contents.Edm
{ {
entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60); entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(60);
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver); return BuildEdmModel(schema.SchemaDef, app.PartitionResolver());
}); });
return result; return result;

49
src/Squidex.Domain.Apps.Read/Contents/GraphQL/CachingGraphQLService.cs

@ -11,65 +11,36 @@ using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Read.Apps; using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Assets.Repositories; using Squidex.Domain.Apps.Read.Assets.Repositories;
using Squidex.Domain.Apps.Read.Schemas.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{ {
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService, IEventConsumer public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService
{ {
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(60);
private readonly IContentQueryService contentQuery; private readonly IContentQueryService contentQuery;
private readonly IGraphQLUrlGenerator urlGenerator; private readonly IGraphQLUrlGenerator urlGenerator;
private readonly IAssetRepository assetRepository; private readonly IAssetRepository assetRepository;
private readonly ISchemaRepository schemaRepository; private readonly IAppProvider appProvider;
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^(schema-)|(apps-)"; }
}
public CachingGraphQLService(IMemoryCache cache, public CachingGraphQLService(IMemoryCache cache,
IAppProvider appProvider,
IAssetRepository assetRepository, IAssetRepository assetRepository,
IContentQueryService contentQuery, IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator, IGraphQLUrlGenerator urlGenerator)
ISchemaRepository schemaRepository)
: base(cache) : base(cache)
{ {
Guard.NotNull(schemaRepository, nameof(schemaRepository)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetRepository, nameof(assetRepository)); Guard.NotNull(assetRepository, nameof(assetRepository));
Guard.NotNull(contentQuery, nameof(urlGenerator)); Guard.NotNull(contentQuery, nameof(urlGenerator));
Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(contentQuery, nameof(contentQuery));
this.appProvider = appProvider;
this.assetRepository = assetRepository; this.assetRepository = assetRepository;
this.contentQuery = contentQuery; this.contentQuery = contentQuery;
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
this.schemaRepository = schemaRepository;
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public Task On(Envelope<IEvent> @event)
{
if (@event.Payload is AppEvent appEvent)
{
Cache.Remove(CreateCacheKey(appEvent.AppId.Id));
}
return TaskHelper.Done;
} }
public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query) public async Task<(object Data, object[] Errors)> QueryAsync(IAppEntity app, ClaimsPrincipal user, GraphQLQuery query)
@ -90,13 +61,13 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
private async Task<GraphQLModel> GetModelAsync(IAppEntity app) private async Task<GraphQLModel> GetModelAsync(IAppEntity app)
{ {
var cacheKey = CreateCacheKey(app.Id); var cacheKey = CreateCacheKey(app.Id, app.Etag);
var modelContext = Cache.Get<GraphQLModel>(cacheKey); var modelContext = Cache.Get<GraphQLModel>(cacheKey);
if (modelContext == null) if (modelContext == null)
{ {
var allSchemas = await schemaRepository.QueryAllAsync(app.Id); var allSchemas = await appProvider.GetSchemasAsync(app.Name);
modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator); modelContext = new GraphQLModel(app, allSchemas.Where(x => x.IsPublished), urlGenerator);
@ -106,9 +77,9 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
return modelContext; return modelContext;
} }
private static object CreateCacheKey(Guid appId) private static object CreateCacheKey(Guid appId, string etag)
{ {
return $"GraphQLModel_{appId}"; return $"GraphQLModel_{appId}_{etag}";
} }
} }
} }

2
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLModel.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl; CanGenerateAssetSourceUrl = urlGenerator.CanGenerateAssetSourceUrl;
partitionResolver = app.PartitionResolver; partitionResolver = app.PartitionResolver();
assetType = new AssetGraphType(this); assetType = new AssetGraphType(this);
assetListType = new ListGraphType(new NonNullGraphType(assetType)); assetListType = new ListGraphType(new NonNullGraphType(assetType));

4
src/Squidex.Domain.Apps.Read/Contents/GraphQL/GraphQLQuery.cs

@ -6,6 +6,8 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL namespace Squidex.Domain.Apps.Read.Contents.GraphQL
{ {
public class GraphQLQuery public class GraphQLQuery
@ -16,6 +18,6 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL
public string Query { get; set; } public string Query { get; set; }
public string Variables { get; set; } public JObject Variables { get; set; }
} }
} }

2
src/Squidex.Domain.Apps.Read/Contents/IContentEntity.cs

@ -11,7 +11,7 @@ using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Read.Contents namespace Squidex.Domain.Apps.Read.Contents
{ {
public interface IContentEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion public interface IContentEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{ {
Status Status { get; } Status Status { get; }

34
src/Squidex.Domain.Apps.Read.MongoDb/EntityMapper.cs → src/Squidex.Domain.Apps.Read/EntityMapper.cs

@ -6,15 +6,15 @@
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using System;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Read.MongoDb namespace Squidex.Domain.Apps.Read
{ {
public static class EntityMapper public static class EntityMapper
{ {
public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers) where T : IMongoEntity, new() public static T Create<T>(SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new()
{ {
var entity = new T(); var entity = new T();
@ -26,60 +26,62 @@ namespace Squidex.Domain.Apps.Read.MongoDb
SetAppId(@event, entity); SetAppId(@event, entity);
return Update(@event, headers, entity); return entity.Update(@event, headers, updater);
} }
public static T Update<T>(SquidexEvent @event, EnvelopeHeaders headers, T entity) where T : IMongoEntity, new() public static T Update<T>(this T entity, SquidexEvent @event, EnvelopeHeaders headers, Action<T> updater = null) where T : IEntity, new()
{ {
SetVersion(headers, entity); SetVersion(headers, entity);
SetLastModified(headers, entity); SetLastModified(headers, entity);
SetLastModifiedBy(@event, entity); SetLastModifiedBy(@event, entity);
updater?.Invoke(entity);
return entity; return entity;
} }
private static void SetId(EnvelopeHeaders headers, IMongoEntity entity) private static void SetId(EnvelopeHeaders headers, IEntity entity)
{ {
entity.Id = headers.AggregateId(); entity.Id = headers.AggregateId();
} }
private static void SetCreated(EnvelopeHeaders headers, IMongoEntity entity) private static void SetCreated(EnvelopeHeaders headers, IEntity entity)
{ {
entity.Created = headers.Timestamp(); entity.Created = headers.Timestamp();
} }
private static void SetLastModified(EnvelopeHeaders headers, IMongoEntity entity) private static void SetLastModified(EnvelopeHeaders headers, IEntity entity)
{ {
entity.LastModified = headers.Timestamp(); entity.LastModified = headers.Timestamp();
} }
private static void SetVersion(EnvelopeHeaders headers, IMongoEntity entity) private static void SetVersion(EnvelopeHeaders headers, IEntity entity)
{ {
if (entity is IEntityWithVersion withVersion) if (entity is IUpdateableEntityWithVersion withVersion)
{ {
withVersion.Version = headers.EventStreamNumber(); withVersion.Version = headers.EventStreamNumber();
} }
} }
private static void SetCreatedBy(SquidexEvent @event, IMongoEntity entity) private static void SetCreatedBy(SquidexEvent @event, IEntity entity)
{ {
if (entity is IEntityWithCreatedBy withCreatedBy) if (entity is IUpdateableEntityWithCreatedBy withCreatedBy)
{ {
withCreatedBy.CreatedBy = @event.Actor; withCreatedBy.CreatedBy = @event.Actor;
} }
} }
private static void SetLastModifiedBy(SquidexEvent @event, IMongoEntity entity) private static void SetLastModifiedBy(SquidexEvent @event, IEntity entity)
{ {
if (entity is IEntityWithLastModifiedBy withModifiedBy) if (entity is IUpdateableEntityWithLastModifiedBy withModifiedBy)
{ {
withModifiedBy.LastModifiedBy = @event.Actor; withModifiedBy.LastModifiedBy = @event.Actor;
} }
} }
private static void SetAppId(SquidexEvent @event, IMongoEntity entity) private static void SetAppId(SquidexEvent @event, IEntity entity)
{ {
if (entity is IAppRefEntity app && @event is AppEvent appEvent) if (entity is IUpdateableEntityWithAppRef app && @event is AppEvent appEvent)
{ {
app.AppId = appEvent.AppId.Id; app.AppId = appEvent.AppId.Id;
} }

34
src/Squidex.Domain.Apps.Read/IAppProvider.cs

@ -0,0 +1,34 @@
// ==========================================================================
// IApps.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
namespace Squidex.Domain.Apps.Read
{
public interface IAppProvider
{
Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(string appName, Guid id);
Task<IAppEntity> GetAppAsync(string appName);
Task<ISchemaEntity> GetSchemaAsync(string appName, Guid id, bool provideDeleted = false);
Task<ISchemaEntity> GetSchemaAsync(string appName, string name, bool provideDeleted = false);
Task<List<ISchemaEntity>> GetSchemasAsync(string appName);
Task<List<IRuleEntity>> GetRulesAsync(string appName);
Task<List<IAppEntity>> GetUserApps(string userId);
}
}

6
src/Squidex.Domain.Apps.Read/IEntity.cs

@ -13,10 +13,10 @@ namespace Squidex.Domain.Apps.Read
{ {
public interface IEntity public interface IEntity
{ {
Guid Id { get; } Guid Id { get; set; }
Instant Created { get; } Instant Created { get; set; }
Instant LastModified { get; } Instant LastModified { get; set; }
} }
} }

17
src/Squidex.Domain.Apps.Read/IEntityWithAppRef.cs

@ -0,0 +1,17 @@
// ==========================================================================
// IEntityWithAppRef.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Read
{
public interface IEntityWithAppRef : IEntity
{
Guid AppId { get; }
}
}

2
src/Squidex.Domain.Apps.Read/IEntityWithCreatedBy.cs

@ -12,6 +12,6 @@ namespace Squidex.Domain.Apps.Read
{ {
public interface IEntityWithCreatedBy public interface IEntityWithCreatedBy
{ {
RefToken CreatedBy { get; set; } RefToken CreatedBy { get; }
} }
} }

2
src/Squidex.Domain.Apps.Read/IEntityWithVersion.cs

@ -10,6 +10,6 @@ namespace Squidex.Domain.Apps.Read
{ {
public interface IEntityWithVersion public interface IEntityWithVersion
{ {
long Version { get; set; } long Version { get; }
} }
} }

6
src/Squidex.Domain.Apps.Read/IAppRefEntity.cs → src/Squidex.Domain.Apps.Read/IUpdateableEntityWithAppRef.cs

@ -1,5 +1,5 @@
// ========================================================================== // ==========================================================================
// IAppRefEntity.cs // IUpdateableEntityWithAppRef.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
@ -10,8 +10,8 @@ using System;
namespace Squidex.Domain.Apps.Read namespace Squidex.Domain.Apps.Read
{ {
public interface IAppRefEntity : IEntity public interface IUpdateableEntityWithAppRef
{ {
Guid AppId { get; set; } Guid AppId { get; set; }
} }
} }

9
src/Squidex.Domain.Apps.Read/Schemas/ISchemaEventConsumer.cs → src/Squidex.Domain.Apps.Read/IUpdateableEntityWithCreatedBy.cs

@ -1,16 +1,17 @@
// ========================================================================== // ==========================================================================
// ISchemaEventConsumer.cs // IUpdateableEntityWithCreatedBy.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Schemas namespace Squidex.Domain.Apps.Read
{ {
public interface ISchemaEventConsumer : IEventConsumer public interface IUpdateableEntityWithCreatedBy
{ {
RefToken CreatedBy { get; set; }
} }
} }

17
src/Squidex.Domain.Apps.Read/IUpdateableEntityWithLastModifiedBy.cs

@ -0,0 +1,17 @@
// ==========================================================================
// IUpdateableEntityWithLastModifiedBy.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read
{
public interface IUpdateableEntityWithLastModifiedBy
{
RefToken LastModifiedBy { get; set; }
}
}

15
src/Squidex.Domain.Apps.Read/IUpdateableEntityWithVersion.cs

@ -0,0 +1,15 @@
// ==========================================================================
// IUpdateableEntityWithVersion.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Domain.Apps.Read
{
public interface IUpdateableEntityWithVersion
{
long Version { get; set; }
}
}

4
src/Squidex.Domain.Apps.Read/Rules/IRuleEntity.cs

@ -10,8 +10,8 @@ using Squidex.Domain.Apps.Core.Rules;
namespace Squidex.Domain.Apps.Read.Rules namespace Squidex.Domain.Apps.Read.Rules
{ {
public interface IRuleEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion public interface IRuleEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{ {
Rule Rule { get; } Rule RuleDef { get; }
} }
} }

2
src/Squidex.Domain.Apps.Read/Rules/Repositories/IRuleEventRepository.cs

@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Read.Rules.Repositories
Task EnqueueAsync(Guid id, Instant nextAttempt); Task EnqueueAsync(Guid id, Instant nextAttempt);
Task MarkSendingAsync(Guid jobId);
Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall); Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall);
Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken)); Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken cancellationToken = default(CancellationToken));

114
src/Squidex.Domain.Apps.Read/Rules/RuleDequeuer.cs

@ -7,11 +7,13 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow; using System.Threading.Tasks.Dataflow;
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.Read.Rules.Repositories; using Squidex.Domain.Apps.Read.Rules.Repositories;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
@ -22,10 +24,10 @@ namespace Squidex.Domain.Apps.Read.Rules
public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem public sealed class RuleDequeuer : DisposableObjectBase, IExternalSystem
{ {
private readonly ActionBlock<IRuleEventEntity> requestBlock; private readonly ActionBlock<IRuleEventEntity> requestBlock;
private readonly TransformBlock<IRuleEventEntity, IRuleEventEntity> blockBlock;
private readonly IRuleEventRepository ruleEventRepository; private readonly IRuleEventRepository ruleEventRepository;
private readonly RuleService ruleService; private readonly RuleService ruleService;
private readonly CompletionTimer timer; private readonly CompletionTimer timer;
private readonly ConcurrentDictionary<Guid, bool> executing = new ConcurrentDictionary<Guid, bool>();
private readonly IClock clock; private readonly IClock clock;
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -44,15 +46,9 @@ namespace Squidex.Domain.Apps.Read.Rules
this.log = log; this.log = log;
requestBlock = requestBlock =
new ActionBlock<IRuleEventEntity>(MakeRequestAsync, new ActionBlock<IRuleEventEntity>(HandleAsync,
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 }); new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 32, BoundedCapacity = 32 });
blockBlock =
new TransformBlock<IRuleEventEntity, IRuleEventEntity>(x => BlockAsync(x),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1, BoundedCapacity = 1 });
blockBlock.LinkTo(requestBlock, new DataflowLinkOptions { PropagateCompletion = true });
timer = new CompletionTimer(5000, QueryAsync); timer = new CompletionTimer(5000, QueryAsync);
} }
@ -62,7 +58,7 @@ namespace Squidex.Domain.Apps.Read.Rules
{ {
timer.StopAsync().Wait(); timer.StopAsync().Wait();
blockBlock.Complete(); requestBlock.Complete();
requestBlock.Completion.Wait(); requestBlock.Completion.Wait();
} }
} }
@ -82,7 +78,7 @@ namespace Squidex.Domain.Apps.Read.Rules
{ {
var now = clock.GetCurrentInstant(); var now = clock.GetCurrentInstant();
await ruleEventRepository.QueryPendingAsync(now, blockBlock.SendAsync, cancellationToken); await ruleEventRepository.QueryPendingAsync(now, requestBlock.SendAsync, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -92,78 +88,70 @@ namespace Squidex.Domain.Apps.Read.Rules
} }
} }
private async Task<IRuleEventEntity> BlockAsync(IRuleEventEntity @event) public async Task HandleAsync(IRuleEventEntity @event)
{ {
try if (!executing.TryAdd(@event.Id, false))
{ {
await ruleEventRepository.MarkSendingAsync(@event.Id); return;
return @event;
} }
catch (Exception ex)
{
log.LogError(ex, w => w
.WriteProperty("action", "BlockWebhookEvent")
.WriteProperty("status", "Failed"));
throw;
}
}
private async Task MakeRequestAsync(IRuleEventEntity @event)
{
try try
{ {
var job = @event.Job; var job = @event.Job;
var response = await ruleService.InvokeAsync(job.ActionName, job.ActionData); var response = await ruleService.InvokeAsync(job.ActionName, job.ActionData);
Instant? nextCall = null; var jobInvoke = ComputeJobInvoke(response.Result, @event, job);
var jobResult = ComputeJobResult(response.Result, jobInvoke);
if (response.Result != RuleResult.Success)
{
switch (@event.NumCalls)
{
case 0:
nextCall = job.Created.Plus(Duration.FromMinutes(5));
break;
case 1:
nextCall = job.Created.Plus(Duration.FromHours(1));
break;
case 2:
nextCall = job.Created.Plus(Duration.FromHours(6));
break;
case 3:
nextCall = job.Created.Plus(Duration.FromHours(12));
break;
}
}
RuleJobResult jobResult;
if (response.Result != RuleResult.Success && !nextCall.HasValue) await ruleEventRepository.MarkSentAsync(@event.Id, response.Dump, response.Result, jobResult, response.Elapsed, jobInvoke);
{
jobResult = RuleJobResult.Failed;
}
else if (response.Result != RuleResult.Success && nextCall.HasValue)
{
jobResult = RuleJobResult.Retry;
}
else
{
jobResult = RuleJobResult.Success;
}
await ruleEventRepository.MarkSentAsync(@event.Id, response.Dump, response.Result, jobResult, response.Elapsed, nextCall);
} }
catch (Exception ex) catch (Exception ex)
{ {
log.LogError(ex, w => w log.LogError(ex, w => w
.WriteProperty("action", "SendWebhookEvent") .WriteProperty("action", "SendWebhookEvent")
.WriteProperty("status", "Failed")); .WriteProperty("status", "Failed"));
}
finally
{
executing.TryRemove(@event.Id, out var value);
}
}
private static RuleJobResult ComputeJobResult(RuleResult result, Instant? nextCall)
{
if (result != RuleResult.Success && !nextCall.HasValue)
{
return RuleJobResult.Failed;
}
else if (result != RuleResult.Success && nextCall.HasValue)
{
return RuleJobResult.Retry;
}
else
{
return RuleJobResult.Success;
}
}
throw; private static Instant? ComputeJobInvoke(RuleResult result, IRuleEventEntity @event, RuleJob job)
{
if (result != RuleResult.Success)
{
switch (@event.NumCalls)
{
case 0:
return job.Created.Plus(Duration.FromMinutes(5));
case 1:
return job.Created.Plus(Duration.FromHours(1));
case 2:
return job.Created.Plus(Duration.FromHours(6));
case 3:
return job.Created.Plus(Duration.FromHours(12));
}
} }
return null;
} }
} }
} }

15
src/Squidex.Domain.Apps.Read/Rules/RuleEnqueuer.cs

@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Read.Rules
public sealed class RuleEnqueuer : IEventConsumer public sealed class RuleEnqueuer : IEventConsumer
{ {
private readonly IRuleEventRepository ruleEventRepository; private readonly IRuleEventRepository ruleEventRepository;
private readonly IRuleRepository ruleRepository; private readonly IAppProvider appProvider;
private readonly RuleService ruleService; private readonly RuleService ruleService;
public string Name public string Name
@ -33,17 +33,18 @@ namespace Squidex.Domain.Apps.Read.Rules
} }
public RuleEnqueuer( public RuleEnqueuer(
IRuleEventRepository ruleEventRepository, IRuleEventRepository ruleEventRepository, IAppProvider appProvider,
IRuleRepository ruleRepository,
RuleService ruleService) RuleService ruleService)
{ {
Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository)); Guard.NotNull(ruleEventRepository, nameof(ruleEventRepository));
Guard.NotNull(ruleRepository, nameof(ruleRepository));
Guard.NotNull(ruleService, nameof(ruleService)); Guard.NotNull(ruleService, nameof(ruleService));
Guard.NotNull(appProvider, nameof(appProvider));
this.ruleEventRepository = ruleEventRepository; this.ruleEventRepository = ruleEventRepository;
this.ruleRepository = ruleRepository;
this.ruleService = ruleService; this.ruleService = ruleService;
this.appProvider = appProvider;
} }
public Task ClearAsync() public Task ClearAsync()
@ -55,11 +56,11 @@ namespace Squidex.Domain.Apps.Read.Rules
{ {
if (@event.Payload is AppEvent appEvent) if (@event.Payload is AppEvent appEvent)
{ {
var rules = await ruleRepository.QueryCachedByAppAsync(appEvent.AppId.Id); var rules = await appProvider.GetRulesAsync(appEvent.AppId.Name);
foreach (var ruleEntity in rules) foreach (var ruleEntity in rules)
{ {
var job = ruleService.CreateJob(ruleEntity.Rule, @event); var job = ruleService.CreateJob(ruleEntity.RuleDef, @event);
if (job != null) if (job != null)
{ {

2
src/Squidex.Domain.Apps.Read/Schemas/ISchemaEntity.cs

@ -10,7 +10,7 @@ using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Read.Schemas namespace Squidex.Domain.Apps.Read.Schemas
{ {
public interface ISchemaEntity : IAppRefEntity, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion public interface ISchemaEntity : IEntityWithAppRef, IEntityWithCreatedBy, IEntityWithLastModifiedBy, IEntityWithVersion
{ {
string Name { get; } string Name { get; }

23
src/Squidex.Domain.Apps.Read/Schemas/Repositories/ISchemaRepository.cs

@ -1,23 +0,0 @@
// ==========================================================================
// ISchemaRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Read.Schemas.Repositories
{
public interface ISchemaRepository
{
Task<IReadOnlyList<ISchemaEntity>> QueryAllAsync(Guid appId);
Task<ISchemaEntity> FindSchemaAsync(Guid appId, string name);
Task<ISchemaEntity> FindSchemaAsync(Guid schemaId);
}
}

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

Loading…
Cancel
Save